Quantcast
Channel: < I runat="server" / >
Viewing all articles
Browse latest Browse all 10

Bold style for matching results with AutoCompleteExtender - Implemented using a Trie

$
0
0

This article will focus on the implementation of the AjaxControlToolkit's AutoCompleteExtender into a simple ASP.NET application. What I'm trying to do here is pull the data from a database and use the AutoCompleteExtender to display the data as the user types characters in a textbox.

Server Implementation

Im not going to focus on how to retrieve the data (from a table of around 60,000 records). I'm going to use the simplistic SQLConnection/SQLCommand/SQLDataReader classes and passing sql command as plain text (as opposed to Stored Procedures). The key issue is how and what is the best way to store the data once being retrieved. Generally, Autocomplete comes in 2 flavours:-

1: Find words based on letters anywhere in a phrase (performing a LIKE '%ar%'  in SQL).

2: Find words that start with specific characters ( LIKE 'ar%' in SQL).

In the first instance, you're pretty much tied up to using the LIKE clause (be it in a stored proc or simple text). The determining factor here is how and when to cache the data since each character typed in invokes the webservice and queries the database. Each query will yield a different result set.

In the second instance(the focus of this article), the more you add characters to the keyword, you are, in fact, narrowing down the scope of search. This means that the data we initially load is deterministic and hence we can cache it somewhere. But the real deal here is :which  data structure do we hold our words in? List<>, DataTable, Linked List??

Trie 

You might have already figured this one out. Anyway, a trie is an ordered tree data structure (a.k.a prefix tree) that stores the information about the contents of each node in the path from the root to the node, rather than the node itself. What this means is that each part between the root and any leaf represents a key and the goal is to find the key by traversing the tree.

Implementation

Eyal Mey-Tal has implemented a Trie structure in C#.Head to the CodePlex site to download it.

WebMethod to retrieve data

[WebMethod]
    public string[] GetNames(string prefixText, int count)
    {
        Trie trie = (Trie)Context.Cache["Trie"];
        if (trie == null)
        {
            trie = new Trie();
            using (SqlConnection con = new SqlConnection("server=(local);database=autocomplete;user id=xxx;Password=xxx;"))
            {
                SqlCommand cmd = new SqlCommand("select word from AutoCompleteData", con);
                cmd.CommandType = System.Data.CommandType.Text;
                con.Open();
                SqlDataReader dr = cmd.ExecuteReader();
                if (dr.HasRows)
                {
                    while (dr.Read())
                    {
                        trie.Add(dr.GetString(0));
                    }
                    con.Close();
                }
            }
            //simple caching here  
            Context.Cache["Trie"] = trie;
        }
        List list = trie.GetCompletionList(prefixText);
        return list.Take(count).ToArray();
    }

 

Setting up the AutoCompleteExtender was easy. To get the autocomplete working, I only had to modify a few of the extender's attributes (.e.g. CompletionSetCount, TargetControlID, ServicePath, ServiceMethod, DelimiterCharacters ..) and data was being returned as I typed in characters. However, making the extender look good was a different story.

Client-Side Implementation 

This is the bit where I'm open to suggestions. I'm no Javascript/JQuery expert but however, I finally got the extender working as I wanted it to. Characters relevant to the ones the user has typed become highlighted in the drop down list. It took me a while to dig this solution out. The javascript is awkward and needs to be refactored. Netherless, the solution works for IE and Firefox [ good enough for me :) ]

 

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="AutoComplete.aspx.cs" Inherits="AutoComplete.AutoComplete" %><%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="cc1" %><!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 id="Head1" runat="server"><title></title><style>
        .autocomplete_completionListElement
        {
            margin: 0px !important;
            background-color: inherit;
            color: windowtext;
            border: buttonshadow;
            border-width: 1px;
            border-style: solid;
            cursor: 'default';
            overflow: auto;
            height: 200px;
            text-align: left;
            list-style-type: none;
            padding-left: 1px;
        }
        /* AutoComplete highlighted item */
        .autocomplete_highlightedListItem
        {
            background-color: #aaff90;
            color: black;
            padding: 1px;
            cursor: hand;
        }
        /* AutoComplete item */
        .autocomplete_listItem
        {
            background-color: window;
            color: windowtext;
            padding: 1px;
        }</style><script type="text/javascript">

        function acePopulated(sender, e) {

            var behavior = $find('AutoCompleteEx');

            var target = behavior.get_completionList();
            if (behavior._currentPrefix != null) {
                var prefix = behavior._currentPrefix.toLowerCase();
                var i;
                for (i = 0; i < target.childNodes.length; i++) {
                    var sValue = target.childNodes[i].innerHTML.toLowerCase();
                    if (sValue.indexOf(prefix) != -1) {
                        var fstr = target.childNodes[i].innerHTML.substring(0, sValue.indexOf(prefix));
                        var pstr = target.childNodes[i].innerHTML.substring(fstr.length, fstr.length + prefix.length);
                        var estr = target.childNodes[i].innerHTML.substring(fstr.length + prefix.length, target.childNodes[i].innerHTML.length);
                        target.childNodes[i].innerHTML = "<div class='autocomplete-item'>" + fstr + '<B>' + pstr + '</B>' + estr + "</div>";
                    }
                }
            }

        }

        function aceSelected(sender, e) {
            var value = e.get_value();
            if (!value) {
                if (e._item.parentElement && e._item.parentElement.tagName == "LI")
                    value = e._item.parentElement.attributes["_value"].value;
                else if (e._item.parentElement && e._item.parentElement.parentElement.tagName == "LI")
                    value = e._item.parentElement.parentElement.attributes["_value"].value;
                else if (e._item.parentNode && e._item.parentNode.tagName == "LI")
                    value = e._item.parentNode._value;
                else if (e._item.parentNode && e._item.parentNode.parentNode.tagName == "LI")
                    value = e._item.parentNode.parentNode._value;
                else value = "";
            }
            var searchText = $get('<%=txtWord.ClientID %>').value;
            searchText = searchText.replace('null', '');

            sender.get_element().value = searchText + value;
        }
                                       
   
    </script></head><body><form id="form1" runat="server"><asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager><div style="text-align: center; margin-top: 200px">
        Search :<asp:TextBox ID="txtWord" runat="server" AutoComplete="off" Width="200px" /></div><cc1:AutoCompleteExtender runat="server" BehaviorID="AutoCompleteEx" ID="autoComplete1"
        TargetControlID="txtWord" ServicePath="MyService.asmx" ServiceMethod="GetNames"
        MinimumPrefixLength="1" CompletionInterval="1000" EnableCaching="true" CompletionSetCount="20"
        CompletionListCssClass="autocomplete_completionListElement" CompletionListItemCssClass="autocomplete_listItem"
        CompletionListHighlightedItemCssClass="autocomplete_highlightedListItem" OnClientPopulated="acePopulated"
        OnClientItemSelected="aceSelected" DelimiterCharacters=";" ShowOnlyCurrentWordInCompletionListItem="true"></cc1:AutoCompleteExtender></form></body></html>

Result

 

 

Downloads

The source code (with database script in ~/App_Data (around 60,000 records )) can be downloaded below.

AutoCompleteVS2008Sln.zip (4.84 mb)

Enjoy ....


Viewing all articles
Browse latest Browse all 10

Trending Articles