jQuery Autocomplete in ASP.NET MVC
This time I'm going to write about using jQuery Autocomplete plugin in ASP.NET MVC. If we want to use the plugin, we should reference following JavaScript and CSS:
<link href="<%= Url.Content("~/Content/jquery.autocomplete.css") %>" rel="stylesheet" type="text/css" />We will start by creating simple autocomplete with static data (below JavaScript assumes that there is already a input with type text and id region):
<script src="<%= Url.Content("~/Scripts/jquery-1.4.1.min.js") %>" type="text/javascript"></script>
<script src="<%= Url.Content("~/Scripts/jquery.autocomplete.min.js") %>" type="text/javascript"></script>
//Static data for autocompleteNow we will extend regions so it contains objects with regionId and regionDescription properties. We want to display the regionDescription in autocomplete and put selected regionId into input of type hidden. To achieve this, we will use autocomplete callbacks for formatting data and result. But first let's change markup:
var regions = ['Eastern', 'Western', 'Northern', 'Southern'];
$(document).ready(function () {
//Adding autocomplete to region input
$("#region").autocomplete(regions, {
//Minimum number of characters a user has to type before the autocompleter activates
minChars: 0,
//The number of items in the select box
max: 4,
//Fill the input while still selecting a value
autoFill: true,
//Only suggested values are valid
mustMatch: true,
//The comparison doesn't looks inside
matchContains: false
});
});
<%: Html.Hidden("regionId", -1) %>And here is modified JavaScript:
<label for="regionDescription">Region:</label> <%: Html.TextBox("regionDescription")%>
//Static data for autocompleteWhat if we want to achieve the same behavior, but with remotely loaded data? We should start by adding markup for new inputs:
var regions = [
{ regionId: 1, regionDescription: 'Eastern' },
{ regionId: 2, regionDescription: 'Western' },
{ regionId: 3, regionDescription: 'Northern' },
{ regionId: 4, regionDescription: 'Southern' }
];
$(document).ready(function () {
//Adding autocomplete to region input
$("#regionDescription").autocomplete(regions, {
//Minimum number of characters a user has to type before the autocompleter activates
minChars: 0,
//The number of items in the select box
max: 4,
//Fill the input while still selecting a value
autoFill: true,
//Only suggested values are valid
mustMatch: true,
//The comparison doesn't looks inside
matchContains: false,
//Callback which will format items for autocomplete list
formatItem: function (data, index, max) {
//Display only descriptions in list
return data.regionDescription;
},
//Callback which will format items for matching
formatMatch: function (data, index, max) {
//Match only descriptions
return data.regionDescription;
},
//Callback which will format result
formatResult: function (data, index, max) {
//Display only description as result
return data.regionDescription;
}
//Callback which will be called when user chooses value
}).result(function (event, data, formatted) {
//Set region id as hidden input value
if (data)
$("#regionId").val(data.regionId);
else
$("#regionId").val('-1');
});
});
<%: Html.Hidden("territoryId", -1)%>The JavaScript isn't that much different, we just need to pass URL instead of array with data (you can also adjust local cache size):
<label for="territoryDescription">Territory:</label> <%: Html.TextBox("territoryDescription")%>
//Adding autocomplete to territoryDescription inputController action will receive three parameters: q - the string we should search for, limit - maximum number of items we should return (equal to max option) and timestamp. The plugin is expecting quite unusual response. It should be a string, where '\n' is a row delimeter and '|' is cells separator. So the action will look like this (I will skip database access code):
$("#territoryDescription").autocomplete('<%: Url.Action("Territories", "Home") %>', {
//Don't fill the input while still selecting a value
autoFill: false,
//Only suggested values are valid
mustMatch: true,
//The comparison doesn't looks inside
matchContains: false,
//The number of query results to store in cache
cacheLength: 12,
//Callback wich will format items for autocomplete list
formatItem: function (data, index, max) {
//Display only descriptions in list
return data[1];
},
//Callback which will format items for matching
formatMatch: function (data, index, max) {
//Match only descriptions
return data[1];
},
//Callback which will format result
formatResult: function (data, index, max) {
//Display only description as result
return data[1];
}
//Callback which will be called when user chooses value
}).result(function (event, data, formatted) {
//Set territory id as hidden input value
if (data)
$("#territoryId").val(data[0]);
else
$("#territoryId").val('-1');
});
public ContentResult Territories(string q, int limit, Int64 timestamp)So our autocomplete is now populated from server. Lets take it one step further, and make territory dependant on region. We will use extraParams for this:
{
StringBuilder responseContentBuilder = new StringBuilder();
IQueryable<Territory> territories = _repository.GetTerritories(q, limit);
foreach (Territory territory in territories)
responseContentBuilder.Append(String.Format("{0}|{1}\n", territory.TerritoryID, territory.TerritoryDescription));
return Content(responseContentBuilder.ToString());
}
//Adding autocomplete to territoryDescription inputAfter that change we will receive on more parameter in our action:
$("#territoryDescription").autocomplete('<%: Url.Action("Territories", "Home") %>', {
//Don't fill the input while still selecting a value
autoFill: false,
//Only suggested values are valid
mustMatch: true,
//The comparison doesn't looks inside
matchContains: false,
//The number of query results to store in cache
cacheLength: 12,
//Extra parameters which will be put into query string
extraParams: {
regionId: function () { return $("#regionId").val(); }
},
//Callback which will format items for autocomplete list
formatItem: function (data, index, max) {
//Display only descriptions in list
return data[1];
},
//Callback which will format items for matching
formatMatch: function (data, index, max) {
//Match only descriptions
return data[1];
},
//Callback which will format result
formatResult: function (data, index, max) {
//Display only description as result
return data[1];
}
//Callback which will be called when user chooses value
}).result(function (event, data, formatted) {
//Set territory id as hidden input value
if (data)
$("#territoryId").val(data[0]);
else
$("#territoryId").val('-1')
});
public ContentResult Territories(string q, int regionId, int limit, Int64 timestamp)This way we have finished our simple sample. There are few more interesting options in this plugin (for example you can attach it to textarea and/or allow multiply selection). Our sample application looks like this:You can download source code from here.
{
StringBuilder responseContentBuilder = new StringBuilder();
IQueryable<Territory> territories = _repository.GetTerritories(regionId, q, limit);
foreach (Territory territory in territories)
responseContentBuilder.Append(String.Format("{0}|{1}\n", territory.TerritoryID, territory.TerritoryDescription));
return Content(responseContentBuilder.ToString());
}