Giving rich styles to auto-complete feature using StyledAutoCompleteExtender

Auto-complete is one of the key features in ajax style applications. As the most popular ajax toolkit for asp.net development, Ajax Control Toolkit provides AutoCompleteExtender control to build the feature without writing any JavaScript codes. The current version of AutoCompleteExtender is more powerful than before because that the user can input multiple words in the same text box by setting delimiter characters now. But it's still not so useful in some kinds of scenarios - in my opinion - since the control can only support plain text as the choices.

"How can we build the auto-complete feature like Google Suggest using AutoCompleteExtender?" The question like this has been asked times by the developers. The answser is "No, we can't" at the curent moment. But I've extended the AutoCompleteExtender to solve the problem.

The control I build which I called "StyledAutoCompleteExtender" extends the AutoCompleteExtender and the behavior class it uses on client-side extends the AutoCompleteBehavior. I keep most of the features the same as before to give the best compatibility. We can use the control just like the current AutoCompleteExtender and get the same result. But the new StyleAutoCompleteExtender control will accept a ItemTemplete value as the html template to display a single item. ItemTemplate gives us the flexibility to show the choices as the styles we want.

For example, we are building the auto-complete features like Google Suggest. What we should do first is to write the declarative code as following:

<asp:TextBox runat="server" ID="myTextBox" Width="400" style="margin:auto;"/>
 
<jeffz:StyledAutoCompleteExtender
    runat="server"
    BehaviorID="AutoCompleteEx"
    ID="autoComplete1"
    TargetControlID="myTextBox"
    ServicePath="AutoComplete.asmx"
    ServiceMethod="GetSearchCompletionList"
    MinimumPrefixLength="2"
    CompletionInterval="1000"
    EnableCaching="true"
    CompletionSetCount="10"
    CompletionListCssClass="completionListElement"
    CompletionListItemCssClass="listItem"
    CompletionListHighlightedItemCssClass="highlightedListItem"
    CompletionListElementID="Panel1">
    <ItemTemplate>
       <span style="float:left;" class="keywords">{0}</span>
       <span style="float:right;" class="result">{1}&nbsp;results</span>
       <div style="clear:both;"></div>
    </ItemTemplate>
</jeffz:StyledAutoCompleteExtender>
 
<asp:Panel ID="Panel1" runat="server"></asp:Panel>

Please focus on the ItemTemplate set in the AutoCompleteExtender. There're format parameters like "{0}" and "{1}" in it. It will be replaced by the value get from the method on server-side - I'll give more information of that later. Actuallly the value of ItemTemplate is the format string witch will be used as the paratemer of String.format() method so we can utilize the localization features in ASP.NET AJAX by setting format parameters as "{0:d}".

The values to replace the format item would be get from the web service method on server-side. Apparently, we'll probably get multiple values for a single chioces so the return type of the method should be modified. The following is web service method used for our "Google Suggest":

[WebMethod]
public IList<object[]> GetSearchCompletionList(string prefixText, int count)
{
    if (count == 0)
    {
        count = 10;
    }
 
    Random random = new Random();
    IList<object[]> items = new List<object[]>(count);
    for (int i = 0; i < count; i++)
    {
        char c1 = (char)random.Next(65, 90);
        char c2 = (char)random.Next(97, 122);
        char c3 = (char)random.Next(97, 122);
 
        items.Add(new object[]
        {
            prefixText + c1 + c2 + c3,
            random.Next(10000, 300000)
        });
    }
 
    return items;
}

The new method should return a list contains an array of objects. Each array will be used to display a single item of choices. The objects in the array will be used as the rest paramesters when calling String.format() method. Please note that if the method returns an array of string as before, the ItemTemplate set in the control would be ignored and everything will work like the traditional AutoCompleteExtender. The final result is just like the following image (the css style defined in the page has been omitted).

The most important modification of StyleAutoCompleteExtender is the separation of "the text to complete user's typing" and "the content in the choices".  In the current version of StyledAutoCompleteExtender, the first object in the array for each choise will be selected as "the text to complete user's typing". That means the first object could be ignored in ItemTemplate if necessary. The following sample is to simulate the email selection when using Windows Live Mail. Please note that the real feature is purely implemented on client-side but we're using StyleAutoCompleteExtender, which will get the choises from server-side.

Here're the defined css styles and the declarative code:

.completionListElement
{ 
    visibility : hidden;
    margin : 0px! important;
    background-color : inherit;
    color : black;
    border : solid 1px gray;
    cursor : pointer;
    text-align : left;
    list-style-type : none;
    font-family : Verdana;
    font-size: 11px;
    padding : 0;
}
.listItem
{
    background-color: white;
    padding : 1px;
}     
.highlightedListItem
{
    background-color: #c3ebf9;
    padding : 1px;
}

<asp:TextBox runat="server" ID="myTextBox" Width="500" style="height:14px;
    border: solid 1px gray; font-family:Verdana; font-size: 11px;" />
 
<jeffz:StyledAutoCompleteExtender
    runat="server"
    BehaviorID="AutoCompleteEx"
    ID="autoComplete1"
    TargetControlID="myTextBox"
    ServicePath="AutoComplete.asmx"
    ServiceMethod="GetEmailCompletionList"
    MinimumPrefixLength="2"
    CompletionInterval="1000"
    EnableCaching="true"
    CompletionSetCount="10"
    CompletionListCssClass="completionListElement"
    CompletionListItemCssClass="listItem"
    CompletionListHighlightedItemCssClass="highlightedListItem"
    DelimiterCharacters=",;">
    <ItemTemplate>{1}</ItemTemplate>
</jeffz:StyledAutoCompleteExtender>

And here's the corresponding web service method. Please note that each item in the list is an array with 2 elements. The first one is the text would be set in the textbox and second one is the html string for a single choise.

private static string[] s_emails = new string[]
{
    "\"jeffrey zhao at yahoo\" &lt;jeffz@yahoo.com&gt;",
    "\"jeffrey zhao at yahoo china\" &lt;jeffz@yahoo.com.cn&gt;",
    "\"jeffrey zhao at live mail\" &lt;jeffz@live.com&gt;",
    "\"jeffrey zhao at gmail\" &lt;jeffz@gmail.com&gt;"
};
 
 
[WebMethod]
public IList<object[]> GetEmailCompletionList(
    string prefixText, int count)
{
    IList<object[]> items = new List<object[]>();
 
    prefixText = prefixText.Trim();
    foreach (string email in s_emails)
    {
        if (email.Contains(prefixText))
        {
            string encoded = email.Replace(
                prefixText, 
"<b>" + prefixText + "</b>");
            items.Add(new object[]
            {
                HttpUtility.HtmlDecode(email) + ";",
                encoded
            });
        }
    }
 
    return items;
}

Since we have set the DelimiterCharacters property int the control we can type multiple words in the textbox. The final result is showing in the following image.

The implementation of StyleAutoCompleteExtender is not so complex as I thought before and I'll explain it in another post. The attachment is the code of the control with two samples. Please feel free to contract me if you found problems and any suggestions would be better. :)

 

13 Comments

  • If I want my AutoCompleteExtender more flexible, for example publishing the property max-height of the completionListElement, how do I get this object and set this property on the server side?
    If I use completionListElementID property refering to any panel on my page (or control), I can get this object, because it's member of the class. But when it's just a property of the AutocompleteExtender, I didn't find a way to get it.
    Could you help me?

  • @Christian
    It's not easy to do that unless you modify the code of the extender. You can get the data from client, pass it to the server-side method, and don't forget to change the signature of the corresponding method in server-side.

  • Jeffrey, this is really nice work. How can i get in touch with you? I'm having a problem running the sample app.

    Thanks,
    Nazim
    nrashid@williamoneil.com

  • @Nazim
    You can contact me at jeffz@live.com

  • Jeffrey,
    Your code works perfectly! I converted it to VB.NET and used in my application just fine. My problem is that I can not use a hyperlink inside ItemTemplate. I want users to be able to click on a hyperlink to navigate of a search page, instead, when they click a hyperlink inside ItemTemplate, it just displays a link in the textbox connected to auto-complete. Any ideas?

    Thanks,
    Steve

  • @Steve K
    I'm afraid it's not so easy to do that. When user click on the whole list the text would be put into the text box and cancel the default behavior. Maybe you can override the "_onListMouseDown" method and try to keep the orginal behavior when the user clicked on the specific item.

  • Thank you for your reply. I overrode initializeCompletionList function of AutoCompleteBehavior. In my version of that function, I did not add handlers for mousedown, mouseup, and click events. I tested this change in Firefox 2.0 and IE 6.5 - looks good. This is not very elaborate approach, let me know if you ever come accross a more elegant solution.

    Thanks,
    Steve

  • how can we extend the AutoCompleteExtender to get d above result in the image.....thnks

  • Do you know of any sample explaining how to do it?
    I have implemented Auto Complete feature by using
    AutoCompleteExtender. It's just I may need to have an extra column,
    just to display some extra information. Still, when I click on the
    item I would like only the first column to populate my textbox.

    Thanks

  • I'm using VS.2005. I'm wondering that if can convert to VS.2005.

  • This is a great implementation, thanks for posting.

  • Thanks a lot.

  • This is awesome! Thanks you!

Comments have been disabled for this content.