jQuery Autocomplete against an MVC driven JSON data provider

Programming

First, a disclaimer, this is basically some prototyping I did to discover how feasible this stuff is in reality. It is not an attempt to provide any sort of framework for consumption of others, but arguably the concepts could be used to that end. Feel free to appropriate any of it at will in any way you see fit. All shout-outs accepted 😀

For the sake of having some context around what I am doing, I have a Data Layer provided by the Microsoft Entity Framework running against an existing database. My MVC application only currently deals with a Client model which is related to the Clients table in my database (I take no responsibility for database naming conventions here, I worked with what I had.) I have a Client Controller, a Client Model (very simplified First, and Last Names; Id, and ClientCode properties relating to columns of similar names in the database), and various Client Views (not surprisingly, an Index, Detail, Edit, Create, and Delete views.)

With the above in place I decided to make a simple Client Picker that would work by the user typing the Client’s Last Name into a textbox and it would autocomplete and narrow the choices as the user typed. The final picked Client should be rendered in the Textbox as “Lastname, Firstname, [ClientCode]”

Well first things first, below is the code for the JsonMarshaller class which wraps calling DataContractJsonSerializer. Basically, you pass it a class that is adorned with a DataContractAttribute and it spits out the contents of the class as a Json encoded string.

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Xml;

namespace MvcSandbox.Library.Helpers
{
public class JsonMarshaller
{
public static string CreateJsonString(object objectToSerialize)
{
var dataContractAttribute = Attribute.GetCustomAttribute(objectToSerialize.GetType(), typeof(DataContractAttribute));

var output = new StringBuilder();
var json = new DataContractJsonSerializer(objectToSerialize.GetType());
var ms = new MemoryStream();
XmlDictionaryWriter writer = JsonReaderWriterFactory.CreateJsonWriter(ms);

json.WriteObject(ms, objectToSerialize);
writer.Flush();

byte[] buffer = ms.GetBuffer();
var bytes = new byte[ms.Length];
for (long l = 0; l < ms.Length; l++)
{
bytes[l] = buffer[l];
}

return Encoding.Default.GetString(bytes).Trim();
}
}
}

At this point let’s wire the JsonMarshaller into an MVC web application. For the sake of argument, I created a Controller to handle this. There is no reason it could not have been handled by the existing Client controller, and to be honest this is where I feel it should be for a consistent URL scheme. But when I started I wanted something slightly different from the standard Client views this was a case of me fighting what MVC was trying to give me by default, structure based on what it did (working with a client) then how it did it (being a data service).

To illustrate, to view a client detail the url would be
http://localhost/Clients/Detail/45

and I wanted to get a list of clients with last names starting with “Min” to look like:
http://localhost/Services/Clients/min

In the end I would have rather it be:
http://localhost/Clients/min

In it’s current state I ended up with:
http://localhost/Services/GetClientsByLastName/min

Routing semantics, do what’s best for your app, I just wanted to explain what I’ve got going on here to avoid confusion as MVC is a big believer in naming by convention.

Below are the Client Model and the Services Controller:

Client Model

using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using MvcSandbox.Library.DataAccess;
using MvcSandbox.Library.Model;
using MvcSandbox.Library.Repositories;

namespace MvcSandbox.Models
{
[DataContract]
public class ClientModel : ModelBase
{
[DataMember]
public int ClientId { get; set; }

[DataMember]
public string FirstName { get; set; }

[DataMember]
public string LastName { get; set; }

[DataMember]
public string ClientCode { get; set; }

public ClientModel() { }

public ClientModel(int clientId)
{
LoadFromDataEntity((Clients)Repository.Get(clientId));
}

public ClientModel(Clients client)
{
LoadFromDataEntity(client);
}

public static List GetAllClients()
{
return (from Clients client in new ClientRepository().FindAll(20)
select new ClientModel
{
ClientCode = client.ClientCode,
ClientId = client.ClientID,
FirstName = client.FirstName,
LastName = client.LastName
}).ToList();
}

private void LoadFromDataEntity(Clients client)
{
DataEntity = client;

if (client == null)
{
ClearModelFields();
return;
}

ClientId = DataEntity.ClientID;
FirstName = DataEntity.FirstName;
LastName = DataEntity.LastName;
ClientCode = DataEntity.ClientCode;
}

public override void UpdateDataEntityFromModel()
{
DataEntity.ClientID = ClientId;
DataEntity.FirstName = FirstName;
DataEntity.LastName = LastName;
DataEntity.ClientCode = ClientCode;
}

public override void ClearModelFields()
{
FirstName = string.Empty;
LastName = string.Empty;
ClientCode = string.Empty;
ClientId = 0;
}
}
}

The Client Model Base Class

using System;
using System.Data.Objects.DataClasses;
using System.Runtime.Serialization;
using MvcSandbox.Library.Helpers;
using MvcSandbox.Library.Repositories;

namespace MvcSandbox.Library.Model
{
[DataContract]
public abstract class ModelBase : IModel
where T : EntityObject
where TU : RepositoryBase
{
protected TU Repository = Activator.CreateInstance(typeof(TU)) as TU;
protected T DataEntity;

#region Abstract Members

public abstract void ClearModelFields();

public abstract void UpdateDataEntityFromModel();

#endregion

#region Virtual Members

public virtual void Save()
{
UpdateDataEntityFromModel();
Repository.Save();
}

public virtual string ToJson()
{
return JsonMarshaller.CreateJsonString(this);
}

#endregion
}
}

Services Controller

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.UI.WebControls;
using MvcSandbox.Library.DataAccess;
using MvcSandbox.Library.Helpers;
using MvcSandbox.Library.Model;
using MvcSandbox.Models;

namespace MvcSandbox.Controllers
{
public class ServicesController : Controller
{
public ActionResult GetClientsByLastName(string id)
{
var clientLists = new ModelList();

if (String.IsNullOrEmpty(id))
{
ViewBag.Output = clientLists;
}
else
{
string lastName = id;

var entities = new it01_FSC1Entities();

var cls = (from client in entities.Clients
where client.LastName.StartsWith(lastName.Trim())
orderby client.LastName
select new ClientModel()
{
ClientCode = client.ClientCode,
ClientId = client.ClientID,
FirstName = client.FirstName,
LastName = client.LastName
}).Take(20);

clientLists.Items.AddRange(cls);
}

Response.ContentType = "application/json";
Response.ContentEncoding = Encoding.UTF8;

ViewBag.Output = JsonMarshaller.CreateJsonString(clientLists);
return View();
}
}
}

Nothing really magical there. I would like to go on record stating that calling the data access layer directly in my Controller was an ugly time-saver, I haven’t moved it over to my ClientRepository where it belongs. The Repository insulates my front end / model code from being tied to the database structure. It uses the Entity Framework classes to populate models for me. The Model Base class illustrates some underpinnings of it which aren’t really relevant to the point of this post.

So basically, the Controller grabs the data, populates a class with a DataContract so I can use the JsonMarshaller to Json encode it. Of note in the controller is where it set’s the Response’s content-type to “application/json” and content-encoding to UTF-8. jQuery love’s it some UTF-8.

Once we have the results Json encoded, it sets an item in the ViewBag for use by the GetClientsByLastName View to pump the Json out to the client.

@{
Layout = null;
}
@Html.Raw(ViewBag.Output)

Heh, so little code on that last piece ‘eh 😀

At this point we are ready to consume the HTTP GET enabled data provider with a ClientSearch partial view. Which, is essentially a blue bar with a autocomplete enabled textbox on it to search for a client by last name.

This was the difficult piece. In retrospect, not because it was difficult but because with jQuery, if you’re not writing it everyday, you are forgetting how to write it everyday. And it’s been a while since I’ve been living the jQuery life, however, like a bicycle, you just get back on and pour through docs and examples until it sticks again.

Without further ado, the ClientSearch partial view:

<script language="javascript" type="text/javascript">
var clientSourceArray = [];

function GetClientData(searchTerm) {
var url = "http://localhost:1909/Services/GetClientsByLastName/" + searchTerm;
clientSourceArray = [];

var jqxhr = $.ajax({
url: url,
type: 'GET',
dataType: 'json',
async: false,
success: function (data, status, xhr) {
$.each(data, function (i, clients) {
$.each(clients, function (i, client) {
var clientValue = client.LastName + ', ' + client.FirstName + ' [' + client.ClientCode + ']';
clientSourceArray.push(clientValue);
});
});
}
});

return clientSourceArray;
}

$(document).ready(function () {

//set up the autocomplete form
$("#ClientLastName").autocomplete({
source: function (searchTerm, callback) { callback(GetClientData(searchTerm.term)); },
delay: 100,
minLength: 2
});

//set up the watermarks
var watermark = "Start Typing a Client's Lastname...";
var inputElement = $("#ClientLastName");
if (inputElement.val() == "") {
inputElement.css("color", "#555555");
inputElement.css("font-style", "italic");
inputElement.val(watermark);
}
inputElement.focus(function () {
if (this.value == watermark) {
$(this).css("color", "");
$(this).css("font-style", "");
this.value = "";
}
}).blur(function () {
if (this.value == "") {
this.value = watermark;
$(this).css("color", "#555555");
$(this).css("font-style", "italic");
}
});
});
</script>

<div class="client-search-bar">@Html.TextBox("ClientLastName", "", new Dictionary() { {"class", "client-last-name-input"} })</div>

So now a few comments about that, first, I’m looking for a code highlighter that works better and easier then the one I have…

Well the actual “html” portion is pretty easy, 1 div that uses the MVC HTML helper class to generate a textbox. I’ll also ignore the Watermarking code because it’s unrelated short of making a nicer looking textbox (arguably.)

The bulk of the work is done by the GetClientData() function, it makes an asynchrounous AJAX call to get the Json encoded data for the autocomplete control and the setting up of the actual autocomplete. Which in the end being pretty trivial as it can take a function that takes a searchTerm and a “Add an item to the list to be searched” callback function. The way it is written here, it works but it could be more succinctly done had I truly been in the jQuery zen place. As demonstrated on http://net.tutsplus.com/tutorials/javascript-ajax/how-to-use-the-jquery-ui-autocomplete-widget/ in particular, Step 4: “Attaching the Autocomplete”

//attach autocomplete
$("#to").autocomplete({

//define callback to format results
source: function(req, add){

//pass request to server
$.getJSON("friends.php?callback=?", req, function(data) {

//create array for response objects
var suggestions = [];

//process response
$.each(data, function(i, val){
suggestions.push(val.name);
});

//pass array to callback
add(suggestions);
});
}

Yeah, well I’ll clean that up later. In the end it wasn’t that bad aside from my jQuery rustiness.

Hope this helps you, I know the journey has helped me!