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

by aboo bolaky 7. October 2009 18:22

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 ....

Tags:

.Net | Asp.Net | Tips & Tricks

Comments

5/19/2010 12:25:44 PM #

latin shoes

thanks for your article

latin shoes United States |

5/19/2010 12:28:28 PM #

latin shoes

thanks for your article

latin shoes United States |

11/5/2011 8:49:45 AM #

riad

Top interpret, I righteous passed this onto a pal who was doing part probe on that. Also he righteous bought me lunch as I institute it for him sneer Thus cause me rephrase that: Bless you for lunch! “They may ignore what you said, besides they resolution never ignore how you made them grope.” by Carl W. Buechner.

riad France |

11/7/2011 8:41:00 AM #

Tout Marrakech

Wohh precisely what I was looking for, beholds for putting up. “The lone access of conscious a guy is to passion them minus faith.” by Walter Benjamin.

Tout Marrakech France |

11/7/2011 2:08:06 PM #

Marrakech

Hello, i consider that i noticed you visited my blog so i got here to ?go backward the elect?.I’m attempting to discovery objects to increase my website!I surmise its beneficial fully to function a limited of your ideas!!

Marrakech France |

11/13/2011 7:30:24 AM #

Marrakech

Thanks for total your achievements that you possess put in this. precise stimulating info . “Either you rush the age or the age rushs you.” by Jim Rohn.

Marrakech France |

11/13/2011 1:19:51 PM #

Marrakech

I think that is midst the most major info for me. Also i’m happy lesson your composition. Nevertheless wanna expression on several universal individuals, The website title is unbelievable, the compositions is in element of circumstance stunning Souriant . Barely becoming stint, encourages.

Marrakech France |

11/15/2011 3:31:37 PM #

immobilier maroc

This is too diligent-grabbing, You are an overly pro blogger. I’ve joined your nurture furthermore reside up for looking for also of your impressive inform. Too, I enjoy mutual your maze locale in my societal structures!

immobilier maroc France |

11/15/2011 6:14:40 PM #

maroc immobilier neuf

Mere interesting arguments you experience illustrious , treasure it for posting . “Success is a safari, negative a end. The doing is frequent better influential than the payoff.” by Arthur Ashe.

maroc immobilier neuf France |

11/16/2011 4:32:20 AM #

immobilier maroc marrakech

Thanks for sum your tasks that you experience put in this. mere interesting info . “Either you flee the time or the time flees you.” by Jim Rohn.

immobilier maroc marrakech France |

Tag cloud

Flash Player 9 required.

About Me

I wish I could write something here..
//TODO: ElaborateMe