Monday, November 18, 2013

Show selected item details after selecting an item from autocomplete dropdownlist using Knockout.js

This is a continuation of previous post where we looked at how to implement autocomplete functionality into Asp.net MVC 4 application using jQuery UI and Ajax.  In the previous post when you selected a particular user it redirected you to a different page.  What if we want to show information about that user on the same page without any page refresh.  In this post we are going to do exactly the same thing.  Well there are many ways to do this but I want to show you the one using Knockout.js. To use Knockout.js first include it in our BundleConfig.cs  

bundles.Add(new ScriptBundle("~/bundles/knockout").Include("~/Scripts/knockout-*"));

Second lets tell our _layout.cshtml page to load knockout.

<body>
@RenderBody()
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryui")
@Scripts.Render("~/bundles/knockout")
@RenderSection("scripts", required: false)
</body>

Add the following javascript below the autocomplete method in Index.cshtml page. This is a PersonViewModel which has properties like LastName, FirstName and BusinessEntityID.

 
//ViewModels Knockout
function PersonViewModel(data) {
this.LastName = ko.observable(data.lastname);
this.FirstName = ko.observable(data.firstname);
this.BusinessEntityId = ko.observable(data.id);
}

We created a ViewModel for the Person Object called PersonViewModel. And ko.observable(data.lastname) means that lastname is an observable property. Next add the following div section where we are going to display selected person’s information.  Inside the span tag see the data-bind=”text: LastName” it will be replaced by the LastName returned from our select function inside autocomplete.

<div style="display: none;" data-bind="visible: true">
<span data-bind="text: LastName"></span>
<span data-bind="text: FirstName"></span>
</div>

In this post I want to just focus on people who are employees. So lets make sure we allow users to search just for Employees. Make appropriate changes suggested below

[HttpPost]
public JsonResult Find(string prefixText)
{
var suggestedUsers = from x in db.People
where x.LastName.StartsWith(prefixText) && x.PersonType == "EM"
select new {
id = x.BusinessEntityID,
value = x.LastName + ", " + x.FirstName,
firstname = x.FirstName,
lastname = x.LastName};
var result = Json(suggestedUsers.Take(5).ToList());
return result;
}

One last thing before we run our application. Replace the window.location.href line with  ko.applyBindings(new PersonViewModel(ui.item));  This will databind our selected person to the datatemplate shown above.

@section scripts{
<script type="text/javascript">
//Javascript function to provide AutoComplete and Intellisense feature for finding Users.
$(document).ready(function () {
$("#SearchString").autocomplete({
source: function (request, response) {
$.ajax({
url: "/Home/Find",
data: "{ 'prefixText': '" + request.term + "' }",
dataType: "json",
type: "POST",
contentType: "application/json; charset=utf-8",
dataFilter: function (data) { return data; },
success: function (data) {
response($.map(data, function (item) {
return {
label: item.value,
value: item.value,
id: item.id,
firstname: item.firstname,
lastname: item.lastname
}
}))
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert(textStatus);
}
});
},
minLength: 2,
select: function (even, ui) {
ko.applyBindings(new PersonViewModel(ui.item));
}
});
});
//ViewModels Knockout
function PersonViewModel(data) {
this.LastName = ko.observable(data.lastname);
this.FirstName = ko.observable(data.firstname);
this.BusinessEntityId = ko.observable(data.id);
}
</script>
}

image


I just wanted to show that it could be done with Knockout. At this point our search button is non-functional. We will make it functional later. One thing to notice here is that the information about the selected user is grabbed from the initial query we did to find suggested users.  To bring in more information we would have to adjust our LINQ query.  And I know what you are thinking right now, “But Mitul, that would bring in all the unnecessary information about other users in which we are not interested.” So we are going to write another method in our HomeController to bring in information just about that selected person. Before doing that let’s update our model to bring in additional information about our selected person.


Open the ADworks.edmx file inside our Models folder and then click anywhere next to the entity and select “Update Model from Database


image


And then check on these entities Employee, Address, BusinessEntityAddress, StateProvince. 


image

Now FindPerson method will return detailed information about the selected person.
        [HttpGet]
public JsonResult FindPerson(int id)
{
var user = (from p in db.People
join e in db.Employees
on p.BusinessEntityID equals e.BusinessEntityID
join be in db.BusinessEntityAddresses on p.BusinessEntityID equals be.BusinessEntityID
join ad in db.Addresses on be.AddressID equals ad.AddressID
join st in db.StateProvinces on ad.StateProvinceID equals st.StateProvinceID
where p.BusinessEntityID == id
select new
{
Id = p.BusinessEntityID,
FullName = p.LastName + ", " + p.FirstName,
LastName = p.LastName,
FirstName = p.FirstName,
JobTitle = e.JobTitle,
HireDate = e.HireDate,
AddressLine1 = ad.AddressLine1,
City = ad.City,
PostalCode = ad.PostalCode,
State = st.Name
}).ToList().First();
if (user == null)
{
HttpNotFound();
}
return Json(user, JsonRequestBehavior.AllowGet);
}

Inside our select function of AutoComplete we are going to make an ajax call [line 31] to this method passing in the BusinessEntityID [line 35] for the Person to FindPerson method [line 33]. Also I have made changes to the PersonViewModel to reflect all the properites returned from the FindPerson method.

@section scripts{
<script type="text/javascript">
$(document).ready(function () {
$("#SearchString").autocomplete({
source: function (request, response) {
$.ajax({
url: "/Home/Find",
data: "{ 'prefixText': '" + request.term + "' }",
dataType: "json",
type: "POST",
contentType: "application/json; charset=utf-8",
dataFilter: function (data) { return data; },
success: function (data) {
response($.map(data, function (item) {
return {
label: item.value,
value: item.value,
id: item.id,
firstname: item.firstname,
lastname: item.lastname
}
}))
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert(textStatus);
}
});
},
minLength: 2,
select: function (even, ui) {
var businessEntityId = ui.item.id;
$.ajax({
url: "/Home/FindPerson",
type: "GET",
data: { "id": businessEntityId}
}).done(function () {
}).success(function (person) {
ko.applyBindings(new PersonViewModel(person));
});
}
});
});
//Person Detailed View Model
function PersonViewModel(p) {
this.Id = ko.observable(p.Id);
this.FullName = ko.observable(p.FullName);
this.LastName = ko.observable(p.LastName);
this.FirstName = ko.observable(p.FirstName);
this.JobTitle = ko.observable(p.JobTitle);
this.HireDate = ko.observable(p.HireDate);
this.AddressLine1 = ko.observable(p.AddressLine1);
this.City = ko.observable(p.City);
this.PostalCode = ko.observable(p.PostalCode);
this.State = ko.observable(p.State);
}
</script>
}

Finally lets update our div to reflect all the udpated user properties.

<div style="display: none;" data-bind="visible: true">
FullName:<span data-bind="text: FullName"></span>
<br/> LastName:<span data-bind="text: LastName"></span>
<br/> FirstName:<span data-bind="text: FirstName"></span>
<br/> HireDate:<span data-bind="text: HireDate"></span>
<br/> Job Title:<span data-bind="text: JobTitle"></span>
<br/> AddressLine1: <span data-bind="text: AddressLine1"></span>
<br/> City: <span data-bind="text: City"></span>
<br/> PostalCode: <span data-bind="text: PostalCode"></span>
<br/> State: <span data-bind="text: State"></span>
</div>

Run the application and search a particular user.


image


In this post, we looked at how to show selected user’s information after search for a user using jQuery UI’s autocomplete method.  Initially to display the selected user we simply data binded the object returned by the select object, we didn’t went to server to find more information.  In the final attempt we did another ajax call to the server to find more information about the user.  In the next post we will look into how to show detailed information about a person using a PartialView.