Google like auto complete suggestions

I like Google’s suggest feature and was thinking of using it in any of my project. ASP.NET’s AJAX Control Toolkit have a similar control (AutoCompleteExtender) which provides a basic items to complete this functionality. I searched about its usage, and found many examples, but I was not satisfied with them. They all were populating only a single field using this extender.

I then came up with an idea, why not fill a contacts detail including its name, email address, and phone numbers using just one extender, but without modifying any provided functionality, so that our code be used with newer versions. Below is what it will look after populating that contact form.

contact

Lets checkout how AutoCompleteExtender works. Below is the description for this on toolkit’s sample site.

AutoComplete is an ASP.NET AJAX extender that can be attached to any TextBox control, and will associate that control with a popup panel to display words that begin with the prefix typed into the textbox.

The dropdown with candidate words supplied by a web service is positioned on the bottom left of the text box.

It says that this control will fetch its data from a webservice, and it can be attached to a TextBox control (it can only be attached with one control). When users starts typing in that TextBox control, it fetches a suggestion list from configured webservice.

So we need two things, one would be our sample contact page, and another a webservice.

using System;
using System.Web;
using System.Collections;
using System.Collections.Generic;
using System.Web.Script;
using System.Web.Script.Serialization;
using System.Web.Script.Services;
using System.Web.Services;
using System.Web.Services.Protocols;
using AjaxControlToolkit;

/// <summary>
/// Summary description for SuggestionService
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService()]
public class SuggestionService : System.Web.Services.WebService {

    public SuggestionService () {

        //Uncomment the following line if using designed components
        //InitializeComponent();
    }

    [WebMethod]
    [ScriptMethod()]
    public string[] GetContacts(string prefixText, int count, string contextKey) {
        List&lt;string&gt; items = new List&lt;/string&gt;&lt;string&gt;();
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        ContactManager manager = new ContactManager();
        List&lt;contact&gt; contacts = manager.GetContacts(int.Parse(contextKey), prefixText, count);
        foreach (Contact c in contacts)
        {
            items.Add(AutoCompleteExtender.CreateAutoCompleteItem(c.FullName, serializer.Serialize(c)));
        }

        return items.ToArray();
    }

}

You will notice some extra adornments on service and web method. You must adorn this service with "ScriptService" attribute, because without it, you can not access it from your client script.

[ScriptService()]
public class SuggestionService : System.Web.Services.WebService {

And our webmethod would return list of string items in JSON format, so it also requires "ScriptMethod" attribute on it.

    [WebMethod]
    [ScriptMethod()]
    public string[] GetContacts(string prefixText, int count, string contextKey) {}

In addition to that you have also noticed that I have returned this data differently. I have used AutoCompleteExtender to create its own entries, because I want to return some extra data from my webservice, so that I can populate all related fields on my contact form. This feature is only available in latest build of AJAX Control Toolkit (1.0.20229.0). It was not available before that. It provides a key-value pair, which is a very useful feature, if we want to display a list of friendly names against internal ids. CreateAutoCompleteItem method takes two argument, first is the text which will be displayed and second is the value which can be used by any custom script on the page.

    items.Add(AutoCompleteExtender.CreateAutoCompleteItem(c.FullName, serializer.Serialize(c)));

Now I had another obstacle to overcome, how to pass a complex data for a contact. Here comes JSON for my help. I serialized my contact objects using JavaScriptSerializer.

So far I have bragged that my contact object is a bit complex, here is what it looks like. Below is my Contact and Phone classes.

/// <summary>
/// Summary description for Contact
/// </summary>
public class Contact
{
    protected int _contactId;
    public int ContactId
    {
        get { return _contactId; }
    }

    protected int _companyId;
    public int CompanyId
    {
        get { return _companyId; }
        set { _companyId = value; }
    }

    protected string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set { _firstName = value; }
    }

    protected string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set { _lastName = value; }
    }

    public string FullName
    {
        get { return _firstName + ' ' + _lastName; }
    }

    protected string _email;
    public string Email
    {
        get { return _email; }
        set { _email = value; }
    }
    public int _homePhoneId;
    public int _workPhoneId;

    protected Phone _homePhone;

    public Phone HomePhone
    {
        get { return _homePhone; }
        set { _homePhone = value; }
    }
    protected Phone _workPhone;

    public Phone WorkPhone
    {
        get { return _workPhone; }
        set { _workPhone = value; }
    }

    public Contact() : this(0, 0, 0)
    {
    }

    public Contact(int id) : this(id, 0, 0)
    {
    }

    public Contact(int id, int homephone, int workphone)
    {
        _contactId = id;
        _homePhoneId = homephone;
        _workPhoneId = workphone;
    }
}

/// <summary>
/// Summary description for Phone
/// </summary>
public class Phone
{
    protected int _phoneId;
    public int PhoneId
    {
        get { return _phoneId; }
    }

    protected string _areaCode;
    public string AreaCode
    {
        get { return _areaCode; }
        set { _areaCode = value; }
    }

    protected string _prefix;
    public string Prefix
    {
        get { return _prefix; }
        set { _prefix = value; }
    }

    protected string _number;
    public string Number
    {
        get { return _number; }
        set { _number = value; }
    }

    protected string _extension;
    public string Extension
    {
        get { return _extension; }
        set { _extension = value; }
    }

    public Phone() : this(0)
    {
    }

    public Phone(int id)
    {
        _phoneId = id;
    }
}

Now we have to work out on our contact page. Below is its basic HTML, this contact form contains, firstname, lastname, email address, and phone number fields. Each phone number in-turn have prefix, area code, number and extension fields.

<div style="border: solid 1px #cccccc;padding: 10px;width:400px">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td style="width: 100px;text-align:right;">First Name:</td>
<td><asp:TextBox ID="txtFirstName" runat="server"></asp></td>
</tr>
<tr>
<td style="width: 100px;text-align:right;">Last Name:</td>
<td><asp:TextBox ID="txtLastName" runat="server"></asp></td>
</tr>
<tr>
<td style="width: 100px;text-align:right;">Home Phone:</td>
<td>
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td><asp:TextBox ID="txtHPAreaCode" runat="server" Columns="3"></asp></td>
<td>-<asp:TextBox ID="txtHPPrefix" runat="server" Columns="3"></asp></td>
<td>-<asp:TextBox ID="txtHPNumber" runat="server" Columns="3"></asp></td>
</tr>
</table>
</td>
</tr>
<tr>
<td style="width: 100px;text-align:right;">Work Phone:</td>
<td>
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td><asp:TextBox ID="txtWPAreaCode" runat="server" Columns="3"></asp></td>
<td>-<asp:TextBox ID="txtWPPrefix" runat="server" Columns="3"></asp></td>
<td>-<asp:TextBox ID="txtWPNumber" runat="server" Columns="3"></asp></td>
<td> x</td>
<td><asp:TextBox ID="txtWPExtension" runat="server" Columns="3"></asp></td>
</tr>
</table>
</td>
</tr>
<tr>
<td style="width: 100px;text-align:right;">Email Address :</td>
<td><asp:TextBox ID="txtEmail" runat="server"></asp></td>
</tr>
</table>
</div>

To use AutoCompleteExtender on this page, first place a "ScriptManager" control, and then place AutoCompleteExtender control and set its TargetControlId to firstname TextBox control. This will add some extra properties in firstname TextBox control. These properties are related to its attached extender, you can find there description on AutoCompleteExtender sample page. We must set some of them to activate its working.

First specify the "ServicePath" and set it to our webservice we just created and then set the "ServiceMethod", which will return list of suggestions, this would be our webmethod in our service. We could also use context key, which can help us in filtering our suggestions. In our case I have used it to pass current company information, so that I can return a particular company’s contacts.

This completes our basic setup. If you test this page now, you will get a suggestion list after typing two letters in firstname field, and when you select any item from that list, only firstname field is set with selected name. All other fields would remain blank.

Now we need to add our magic code, which will enable us to achieve our goal.

<script type="text/javascript">
    function OnContactSelected(source, eventArgs)
    {
        var results = eval('('  + eventArgs.get_value() + ')');
        $get('txtFirstName').value = results.FirstName;
        $get('txtLastName').value = results.LastName;
        if (results.HomePhone.AreaCode != null)
            $get('txtHPAreaCode').value = results.HomePhone.AreaCode;
        if (results.HomePhone.Prefix != null)
            $get('txtHPPrefix').value = results.HomePhone.Prefix;
        if (results.HomePhone.Number != null)
            $get('txtHPNumber').value = results.HomePhone.Number;
        if (results.WorkPhone.AreaCode != null)
            $get('txtWPAreaCode').value = results.WorkPhone.AreaCode;
        if (results.WorkPhone.Prefix != null)
            $get('txtWPPrefix').value = results.WorkPhone.Prefix;
        if (results.WorkPhone.Number != null)
            $get('txtWPNumber').value = results.WorkPhone.Number;
        if (results.WorkPhone.Number != null)
            $get('txtWPExtension').value = results.WorkPhone.Extension;
        if (results.Email != null)
            $get('txtEmail').value = results.Email;
    }
</script>

But before that we need to add some client-side behavior to our extender, we need to set "OnClientItemSelected" attribute of our extender to above JavaScript function. This function would parse the value attribute, which is a valid JSON data passed from our webservice, and set appropriate fields with this parsed data.

This conclude our sample, now you can test it yourself. I have not finished it yet, I wanted to format suggestion list a bit further. But as I mentioned earlier, I like to have my solution workable with future releases of AutoCompleteExtender, so I did not altered this component. I would have liked that either the display items can have HTML of their own, so it provides more display customization. Anyway I will try to come up with any solution for this too in future.

I have yet not figured out how to provide full source code for this sample, would do that soon.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.