April 29, 2009

jQuery and MVC Autosuggest while saving the selected Id

Yea, I know, boring title. But I really want to make sure I can find the code for how to do this one.

jQuery is very cool. All the eye candy is nice, the syntax is excellent, but the real benefit is AJAX...

The autocomplete jQuery library is extremely nice. It allows you to take a Json result from some url and suggest results to a text box. What I want is for the selected value to populate another input field (hidden) so that I have a real foreign key relationship to the item they selected.

here's the html:
<input class="location" id="LocationName">
<input id="LocationId" type="hidden" keyFor="LocationName"/>

Or alternately you can use Html Helpers

<%= Html.TextBox("LocationName", null, new {class = "location"}) %>
<%= Html.Hidden("LocationId", null, new {keyFor = "LocationName"})%>

and here is the supporting javascript:

$(document).ready(function() {
$('.location).autocomplete("/location/JsonFind",
{ dataType: 'json',
parse: parseData,
formatItem: formatItem,
formatResult: formatItem
}).result(autocompleteResult);
});

function parseData(data) {
var rows = new Array();
for (var i = 0; i < data.length; i++) {
rows[i] = { data: data[i], result: data[i].Description, value: data[i].Id };
}
return rows;
}

function formatItem(row, i, n) { return row.Description; }

function autocompleteResult(event, data, formatted) {
var TargetId = "[keyFor=" + event.currentTarget.id + "]";
$(TargetId).val(data.Id);
}

and finally, the code in the Location controller

public ActionResult Find(string q, int limit) {
return Json(Repository.GetAll().Where(l => l.LocationName.StartsWith(q)).Take(limit));
}

What's going on here?

Well, we simply have an input control and a hidden intput control on our html page. The important convention I have is that the hidden input control has a non-standard "locationidfor" tag so that we can identify where to put the selected value.

The "location" controller is providing a method that returns complete Location records from our Linq to SQL table through a repository. The exercise of setting up Linq to SQL is left to the reader. The linq code at the end simply grab all the Locations where the location names start with the text the user typed in, and we only take "limit" elements from the list. Your method will probably be different.

jQuery is the glue that binds the whole thing together. Our applyLocationAutosuggest function may be applied in the page ready just as easily, but here I've encapsulated the functionality so it can be applied to jQuery templates as well!

In its simplest form Autocomplete just needs a list of strings (simple matching), but here we use the formatItem and formatResult parameters to make sure that the user doesn't see the entire Location row, just the text we want. Mix in a check to make sure that there was at least one result. There are also two undocumented parameters we need to use, the dataType parameter is set to 'json' because autocomplete expects plain text by default; parse gives us the opportunity to take our json data and change it into the autocomplete internal array format.

Finally, when the result function allows us to take the selected Json result, decode it into an object, and put the id in the hidden field.

Mission Accomplished.