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



The source code for the following post can be downloaded here.

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) thats 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 Trüter", "Eugene Stander",
    "Roland Cooper", "Alexander Mehlhorn",
    "Derek Campher", "Julie Trüter",
    "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 Trüter", 
            "Eugene Stander",
            "Roland Cooper",
            "Alexander Mehlhorn",
            "Derek Campher",
            "Julie Trüter",
            "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







Comments



http://net-informations.com

Code sample... http://net-informations.com/q/faq/autocomplete.html zene


wow...

After a week of searching online... you are the man, thank you.


I love you man!!!

Thank You!!! Thank You!!! Thank You!!! This problem got me so emotional....*phew*.... your solution worked straight away and easy. thanks


Thanks mate

Well done and thanks for sharing. Nice and light and easy to understand.


Namespaces

Hi Yug That could mean that you're not passing the right object to the function. If you're using a PageMethod AutoComplete('<%=txtTest.ClientID %>', 10, 500, PageMethods, 'GetList'); If you're using a Service AutoComplete('<%=txtTest.ClientID %>', 10, 500, MyNamespace.MyService, 'GetList');


Getlist Error

Hi, I am trying to implement but getting Getlist error null or undefined. Please can you help me Kind regards, Yug


UserControl

Super, I look forward to continuing. Tom


RE: in UserControl

Hi Tom Yes most definitely, in fact I've even converted it into a ServerControl. I am actually busy writing part 3 of this "series", which revolves around this idea and should be available by the end of the week (and will be sure to include how to use this in an UserControl) The main "catch" around using it as a reusable AutoComplete UserControl is how to handle the client side load event, everything else is pretty much straightforward.


in UserControl

Hi, is there a way to use it in UserControl?


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


1 2 Last / 2 Pages (12 Entries)

Post comment

Name *
Email
Title
Body *
Security Code
*
* Required fields

Related Posts

Latest Posts

MS SQL: Parameter Sniffing


2012-05-21 22:38:48

Be the best stalker you can be


2011-12-13 22:33:54

Top 5 posts

Moving items between listboxes in ASP.net/PHP example


Move items between two listboxes in ASP.net(C#, VB.NET) and PHP
2008-06-12 17:07:43

Simple WYSIWYG Editor


Creating a WYSIWYG textbox for your website is actually quite simple.
2007-02-01 12:00:00

C# YouTube : Google API


Post on how to integrate with YouTube using the Google Data API
2011-03-12 08:37:51

Populate a TreeView Control C#


Populate a TreeView control in a windows application.
2009-08-27 16:01:03

Cross Browser Issues: Firefox Word Wrapping


Firefox word wrapping issues
2008-06-09 09:51:21