Saturday, March 29, 2008

Creating Custom Auto Complete Content Loading Functionality Using ASP.NET AJAX

You can download the source code for this article, or see it live.

As more developers move to AJAX and start using it's controls, I noticed - the more they are bound to AjaxControlToolkit controls. Often I can see they use AjaxControlToolkit controls in places where it is not needed at all, sometimes, in places where the problem could be solved more efficiently, using very simple script.

I want to remind that AjaxControlToolkit and in fact any toolkit is designed in the way it can be used in some scenarios, and to make control that answers more requirements it could often contain more code than it is needed for some custom little task. The more universal control is, the more code it uses (true but not always). The more code it contains the less performance it provides (true but not always), the more complex control is - the more complex is to use it (again it may be not true for everything - but mainly it is true).

I am participant on ASP.NET forums, and often see when people try to use controls in places for which the control was not designed.

I am not an angel also. I caught myself several times trying to solve problem quickly with some control that is already done, but paid for this with performance degradation and in the end with large amount of spent time. I am really happy when I see that I do something wrong. I am by nature the person who can change mind in the last second, and after rethinking and healthy criticizing my actions can rewrite a good bunch of code to make it right.

 

Sorry for long preface,

Lets create a simple auto complete - like functionality.

On the page I have one input control and one div element. I want to show list of links in the div when typing something in text input control. The list should be loaded from the server.

Here is the markup of a page:

<%@ Page Language="C#" AutoEventWireup="true" 
    CodeBehind="Default.aspx.cs" Inherits="Loading_Contents_While_Typing_Ajax._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
            <Services>
                <asp:ServiceReference Path="~/AutoComplete.asmx" />
            </Services>
        </asp:ScriptManager>
        <script type="text/javascript">
        // time in milliseconds to wait after last character typed by user
        var _WAIT_TIME = 300;
        var _timeoutFunction;
        var _executor = null;
        Sys.Net.WebRequestManager.add_invokingRequest(onInvokingRequest); 
        function onTxtKeyUp() {
            _timeoutFunction = setTimeout(performSearch , 300);
        }
        function onTxtKeyDown() {
            if(_timeoutFunction)clearTimeout(_timeoutFunction);
        }
        function performSearch() {
            if(_executor) {
                _executor.abort();
                _executor = null;
            }
            AutoComplete.GetHypherLinks($get("txtText").value, onComplete, onError);
        }
        function onInvokingRequest(sender, args) {
            _executor = args.get_webRequest().get_executor();
        }
        function onComplete(result) {
            // do whatever you want with result from web server
            $get("divOutput").innerHTML = result
            _executor = null;
        }
        function onError() {
            // place error handling code here
            _executor = null;
        }
        </script>
        <input onkeydown="onTxtKeyDown()" onkeyup="onTxtKeyUp()" 
            type="text" id="txtText" value="" />
        <div id="divOutput"></div>
    </form>
</body>
</html>

And here is a sample webService:

using System.Web.Services;
using System.Web.Script.Services;
using System.Collections.Generic;
using System.Text;
using System;
[WebService]
[ScriptService]
public class AutoComplete : System.Web.Services.WebService
{

    [WebMethod]
    public string GetHypherLinks(string input)
    {
        return GetFormattedList(GetHypherLinksInternal(input));
    }

    private Dictionary<string, string> GetHypherLinksInternal(string input)
    {
        Dictionary<string, string> dic = new Dictionary<string, string>();

        // test results - add real implementation later
        for (int i = 0; i < 5; i++)
        {
            dic.Add(String.Format("{0}_{1}", input, i), String.Format("{0}_{1}", input, i));
        }

        return dic;
    }

    private string GetFormattedList(Dictionary<string, string> links)
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendLine("<ul>");
        foreach (string text in links.Keys)
        {
            sb.AppendLine(String.Format("<li><a href='{0}'>{1}</a></li>", links[text], text));
        }
        sb.AppendLine("</ul>");
        return sb.ToString();
    }
}

 

 

And one more note in the end - In some places and some projects using single control may be preferred, Suppose for example you have used this control in a 1000 places in the project, and you want to change one behavior aspect in all 1000 places, by changing something in one place. - ok here you have to use the control. But anyway I would write my own for such a simple tasks.

Any control has it's "application" area, and range where you can use it and where using it does not make sense is important to understand.

 

Hope this helps.

Technorati Tags:

2 comments:

Anonymous said...

nice work. i was doing something similar with the updatepanel and onloading event, but i think this is more efficient. where in your code would you put the progress indicator?

Anonymous said...

It was very interesting for me to read this article. Thanks for it. I like such themes and anything connected to this matter. I definitely want to read a bit more on that blog soon.