ASP.NET(C#) : Autocomplete TextBox - Part 1 (From Scratch)

March 27, 2011 by C#   ASP.NET  

Back in 2007 I had the wonderful privilege to work at Web Africa, one of the largest internet service providers in South Africa.

One of my PHP projects (or at least a small part of the project) involved creating an autocomplete textbox when searching for domains located within the company MySQL database.

If memory serves me correctly (it probably doesn't) that's around the same time Google introduced Google suggests at Google labs - something that is standard to its search these days.

love google autocomplete
(subtle attempt at humour, ahhh.... ha ha)

Last month we needed the same functionality in one of our ASP.NET projects at my current company.

In this post we're going to have a look at how to create our very own autocomplete textbox using ASP.net (C#) (with a little bit of JavaScript of course).

Lets jump into some code...

We can retrieve our autocomplete results via PageMethods (ajax request, json response), which we need to enable in our scriptmanager at the top of our page like seen in the following snippet:

<asp:ScriptManager runat="server" ID="scriptmanager" EnablePageMethods="true">
    <Scripts>
        <asp:ScriptReference Path="~/js/autocomplete.js" />
    </Scripts>
</asp:ScriptManager>

The server side method responsible for retrieving data will look something like this (you will obviously want to retrieve results via some database - this example is solely for demo purposes)

[WebMethod]
public static string[] GetList(string prefixText, int count)
{
    // Dummy data - don't do this
    string[] data = new string[] { 
    "Christoff Truter", "Gerhardt Stander",
    "Roland Cooper", "Alexander Mehlhorn",
    "Derek Campher", "Jurgens Truter",
    "Hanno Coetzee", "Wayne Kleynhans",
    "Pieter Du Plooy", "Pam Nizar" };

    return (from p in data
            where p.IndexOf(prefixText, StringComparison.OrdinalIgnoreCase) >= 0
            select p).Take<String>(count).ToArray();
}

Alternatively results can also be retrieved via webservice, we need to register the service using the scriptmanager - this makes its methods available to our javascript script. (retrieving our results using a webservice is a bit more reusable than the PageMethod option, since PageMethods are only available within the page they're created on)

<asp:ScriptManager runat="server" ID="scriptmanager">
    <Services>
        <asp:ServiceReference Path="~/MyService.asmx" />
    </Services>
    <Scripts>
        <asp:ScriptReference Path="~/js/autocomplete.js" />
    </Scripts>
</asp:ScriptManager>

The webservice code will look something like this:

using System;
using System.Linq;
using System.Web.Services;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

[System.Web.Script.Services.ScriptService]
public class MyService : System.Web.Services.WebService
{
    [WebMethod]
    public string[] GetList(String prefixText, Int32 count)
    {
        // Dummy data - don't do this
        string[] data = new string[] { 
            "Christoff Truter", 
            "Gerhardt Stander",
            "Roland Cooper",
            "Alexander Mehlhorn",
            "Derek Campher",
            "Jurgens Truter",
            "Hanno Coetzee",
            "Wayne Kleynhans",
            "Pieter Du Plooy",
            "Pam Nizar" };

        return (from p in data
                where p.IndexOf(prefixText, StringComparison.OrdinalIgnoreCase) >= 0
                select p).Take<String>(count).ToArray();
    }
}

Note the ScriptService attribute at the top of the MyService class, its important to set this attribute in order to make the service useable via our scriptmanager and expose it to JavaScript.

Okay, now that we've got an idea of where our data will be coming from, lets have a look at the JavaScript (js/autocomplete.js) script that will make this all possible.

Within our autocomplete.js file we've got a function called AutoComplete, now this function needs to be called as soon as the page is done loading - we can do this using the pageLoad function which will automatically be called by the scriptmanager as soon as the page finished loading, observe:

<script type="text/javascript">

    function pageLoad() {
        AutoComplete('<%=txtTest.ClientID %>', 10, 500, PageMethods, 'GetList');
    }

</script>
<asp:TextBox ID="txtTest" runat="server"></asp:TextBox>

The first argument to the AutoComplete function contains the ID of the textbox we wish to autocomplete, the second the maximum amount of results to return, third the timeout - how long the script needs to wait (in milliseconds) before it attempts to query the server.

The fourth argument is where it gets a little bit more interesting, when using PageMethods an object named PageMethods becomes available to our scripts, when using a webservice the scriptmanager will create an object thats got the same name as the class name we gave our webservice.

In this example the object will be named MyService (if you use namespaces you'll need to include that in your object as well e.g. MyNamespace.MyService), now instead of passing the PageMethods object to the function, you can alternatively pass the MyService object to the function (these objects are interchangeable).

The last argument obviously tells the function which method to call e.g. GetList.

Lets dig a bit deeper to see how all of this works.

(js/autocomplete.js)
function AutoComplete(targetId, count, timeout, service, method) {

    var timeoutId;
    var request;
    var target = document.getElementById(targetId);
    var list = CreateList(target);

    $addHandler(target, "keyup", function() {
        if (timeoutId != undefined) {
            clearTimeout(timeoutId);
        }
        if (request != undefined) {
            var executor = request.get_executor();
            if (executor.get_started()) {
                executor.abort();
            }
        }
        timeoutId = setTimeout(function() {
            request = service._staticInstance[method](target.value, count, success, function() { });
        }, timeout);
    });

Notice the $addHandler function, this is function available courtesy of the scriptmanager - since browsers like IE and FireFox handle events differently, we need a crossbrowser friendly function to attach events.

Within our keyup eventhandler you will notice a few interesting things:
  1. We need to minimize the amount of requests to the server therefore we clear our timer (set using setTimeout) callback with every keyup, if we don't do this every key pressed will do a request to the server like seen in the following image:

    Firebug autocomplete sample one

  2. We also abort requests already in progress, we ideally only want one active request at a time - especially since we're limited to the amount of requests in our browsers - if a key is pressed and an active request is found, the script aborts it, like seen in the image below:

    Firebug autocomplete sample two

The success function like seen below (nested within the AutoComplete giving it access to all its "private" fields), gets executed if the request successfully executes - populating the autcomplete listbox.

function success(result) {
        list.options.length = 0; // clear listbox
        if (result != '') {
            list.setAttribute("size", result.length + 1);
            list.style.display = "block";
            for (var index = 0; index < result.length; index++) {
                list.options.add(new Option(result[index], result[index], false));
            }
        } else {
            list.style.display = "none";
        }
    }

In the following snippet we attach a handler to the page which assigns the selected item to the specified target and hides the active listbox.

$addHandler(document, "click", function(e) {
        if ((e.srcElement == target) || (e.target == target)) {
            return false;
        }
        list.style.display = "none";
        if (list.value != '') {
            target.value = list.value;
        }
    });
}

// Append the autocomplete listbox to the target textbox

function CreateList(sender) {
    var div = document.createElement("div");
    var list = document.createElement("select");
    list.style.position = "absolute";
    list.style.display = "none";
    div.appendChild(list);
    sender.parentNode.insertBefore(div, sender.nextSibling);
    return list;
}

Other things one can look at is caching the ajax results in order to free the server up a bit more etc...

In the next part of this post we're going to have a quick look at how to use an autocomplete extender control that microsoft created (which should represent a more mature solution).

Additional Reading
AjaxControlToolkit : Autocomplete


Leave a Comment


December 20, 2011 by Christoff Truter

Hi there a link to the source code is included in the post, but here it is again http://cstruter.com/downloads/download.php?downloadid=36

Please mail me the js file for auto complete December 20, 2011 by Anonymous

I am trying to autocomple with the help of handler but no succeded.please send me the js file for help.

Thx October 4, 2011 by Anonymous

Thank's too much ! I search since two days and nothing would work ! But your code is good :D


Related Posts

ASP.NET(C#) : Autocomplete TextBox - Part 2 (AjaxControlToolkit)

April 10, 2011


Related Downloads

ASP.NET webforms autocompletion demos