Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Building a Better AutoComplete User Control

4.14/5 (9 votes)
21 Apr 2009CPOL6 min read 47.4K   1.3K  
Expand the usefulness of the AutoComplete Ajax extender

Introduction

After using Facebook for a while, I became intrigued by how they did their text-search functions on several pages. The functionality that I really was interested in was their "live" lookup that then resulted in a list of filtered items. Once you select an item, then an asynch post-back occurs and an update panel on the page gets changed. This is a very slick and light-weight way of achieving a pretty complex series of control behaviors. Also, it is very intuitive (something I'm quite concerned about). So, I started building a similar control and have come up with a pretty nice user control.

sampleimage.png

Background

This started as a single control on a *.aspx but quickly I saw that I wanted it to be "reusable" so I started genericizing it. Of course, that was much harder than I anticipated. 

Critical Part #1 - ListSearch.asmx

First, you need to create a Web Service for your site that will be called by the AutoComplete extender. There are a lot of articles out there on how to do this. I chose to create a ListSearch web service that could contain any number of WebMethods in it.  For this sample, I just have one, but I've left a sample of a typical database call too.

Once a dataset is full, you need to send a Text/Value pair to the AutoComplete object. The magic for doing this is to create a ListOf variable and fill it up with your value pairs like this:

VB.NET
Dim items as New List(Of String)
For Each datar As DataRow In ds.Tables(0).Rows
       items.Add(AjaxControlToolkit.AutoCompleteExtender.CreateAutoCompleteItem
	(datar.Item("Color"), datar.Item("ID")))
Next
Return items.ToArray() 

Critical Part #2 - DataTextSearch.ascx

Once you have created your Web Service, you are ready to build your User Control. There are a ton of problems that came up during this part. Just putting a text box with an AutoComplete extender on a *.aspx is quite easy. The biggest problem I ran into was trying to create a data entry object that could reside on the same page multiple times. You see, the AutoCompleteExtender relies heavily on JavaScript, and when there are more than one of these on a page, you need more than one "version" of the script that refers to the controls in your UserControl uniquely.

So, first, on the ascx side, you will need several things:

  • Text box - This holds the value the user will type in
  • UpdatePanel - I put the entire control in an update panel, so changes to this control do not necessarily effect the "outside" or consuming page of the UserControl
  • TextBoxWatermarkExtender - Just a little flare to make the control a bit friendlier
  • Hidden Button - When a value is selected from the AutoCompleteExtender and UserControl has been configured to do so, we will use this button to "trick" the control into submitting this form. This will allow us to blend JavaScript and Asynch postback behavior.
  • Result Panel - Holds the results from the Extender - starts out being hidden and is drawn based on the AutoCompleteExtenders methods
  • Hidden ID Field - This holds the ID of the text that is selected 
  • AutoCompleteExtender - The work-horse for this control that handles all of the Ajax-y showing/hiding/onclicking/etc.

The configuration and styling of these controls is pretty straight-forward.  So, I'll leave you to browsing my sample for the particulars.

Critical Part #3 - DataTextSearch.ascx.vb 

When the control first loads, if we are not posting back, there is a bunch of things to do.  Here is the breakdown: 

  • Set the width of the objects according to the exposed Property of the user control called BoxWidth:
    VB.NET
    SBox.Width = CType(BoxWidth, Unit)) 
    BoxFrame.Style("width") = BoxWidth.ToString()
  • Choose which WebMethod will be used to collect the information.  I've left this as a choice because your ListSearch.asmx could contain as many methods as you'd like and then you can just tell this UserControl which search you want values back from.  Now, I've created a separate Sub to handle this configuration because I used a Public enum to number the methods I want the UserControl to "know" about. This makes adding the control to the *.aspx page nice, because as you type - intellisense will offer you "choices" of available web methods.
    VB.NET
    Select Case ServiceMethod
        Case ServiceMethods.Colors
            autoComplete1.ServiceMethod = "GetColorsByName"
        Case ServiceMethods.States
            autoComplete1.ServiceMethod = "GetStatesByName"
    End Select

    Here is the Public Enum:

    VB.NET
    Public Enum ServiceMethods
        Colors = 1
        States = 2
    End Enum
  • Turn Highlighting On or Off and configure the WaterMarkText according to the UserControl configuration:
    VB.NET
    If HighlightResults Then
        autoComplete1.OnClientPopulated = "ClientPopulated"
    End If
    
    If WaterMarktext.Length > 0 Then
        textPrompt.WatermarkText = WaterMarktext
    Else
        textPrompt.Enabled = False
    End If
  • The trickiest part - set up the JavaScript needed to handle the client-side events. This is necessary because we are building a user control which could be used multiple times on the same page. So, we get a reference to the ClientID of the fields of the control and set up a JavaScript function (also named uniquely according to the UserControlID) and register it.
    VB.NET
    Dim script As String = "function ControlClickSubmit_" & autoComplete1.ClientID & _
    		     "(source, e) " & _
                           "   { " & _
                           "    var node; " & _
                           " var value = e.get_value(); " & _
                           " if (value) node = e.get_item(); " & _
                           "       else " & _
                           " { " & _
                           "   value = e.get_item().parentNode._value; " & _
                           "   node = e.get_item().parentNode; " & _
                           " } " & _
                           " var text = (node.innerText) ? node.innerText : _
    		     	(node.textContent) ? node.textContent : _
    			node.innerHtml; " & _
                           " source.get_element().value = text; " & _
                           "     document.getElementById_
    			('" & HiddenCID & "').value = _
    		     " value; " & _
                           "     var btn = document.getElementById_
    			('" & subBtn.ClientID & "'); " & _
                           "     var bx = document.getElementById_
    			('" & SBox.ClientID & "'); " & _
                           "     bx.value = text; " & _
                           "     btn.click();}"
  • Set the OnClientItemSelected event of the AutoCompleteExtender:
    VB.NET
    autoComplete1.OnClientItemSelected = _
    	"ControlClickSubmit_" & autoComplete1.ClientID
  • Register the script you created on the page:

    VB.NET
    Page.ClientScript.RegisterClientScriptBlock(Page.GetType(), _
    	"ControlClickSubmit_" & autoComplete1.ClientID, script, True)
  • Create a Public Event called ValueSelected. This can be raised to the .aspx level and used just like any other "normal" event:
    VB.NET
    Public Event ValueSelected(ByVal sender As Object, ByVal e As System.EventArgs)
  • When we put our button on the page, we gave it two very important attributes. One makes sure the button doesn't submit (UseSubmitBehavior="false") and the other binds the button's click event to our .vb code sub (OnClick="SelectAction"). In the JavaScript we registered above, you will notice we .click() a button var. The button we are clicking is this hidden button - and doing so will result in an Asynch postback allowing us to manipulate things on the server side without posting back the entire consuming page (*.aspx). The SelectAction sub is pretty boring. Just sets the selected value to the Hidden ID field, sets the text of the search box to the text of the item selected, and depending on the Control's configuration raises the ValueSelected event. This is the really sweet part. If you raise that ValueSelected Event, you can then write code in your consuming *.aspx to listen for that even and if it occurs carry out other operations. I have a sample of this in the Default.aspx page.

Critical Part #4 - JavaScript Include

I was able to abstract the highlighting behavior for the Autocomplete. That is in a separate js/Autocompletescript.js file that needs to be included on any consuming page. You DO NOT put a reference to this script file in the user control. It would work if you only used the control once per page, but of course, I wanted to be able to put this control on the same page multiple times.

Critical Part #5 - Register the Control in your Web.Config

I don't like to put references in the header of my *.aspx pages, so I typically register the thing in my web.config like this:

XML
  <system.web>
    <pages>
      <controls>
        <add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, 
	    Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
        <add tagPrefix="uc1" src="~/UserControls/DataTextSearch.ascx" 
		tagName="DataTextSearch"/>
          <add assembly="AjaxControlToolkit" namespace="AjaxControlToolkit" 
		tagPrefix="cc1"/>
      </controls>
    </pages>

Critical Part #6 - Default.aspx - The Consumer 

Now that the control is built and properly referenced in our web.config, we can add it to a consuming page. The default.aspx page references the control and then does "stuff" once a list item is selected. 

ASP.NET
<uc1:DataTextSearch runat="server" 
        ID="ColorSearchControl" 
        WaterMarktext="Type a color"
        OnClickSubmit="true" 
        ServiceMethod="Colors"
        HighlightResults="true"
        ClearAfterSelect="true" />

Critical Part # 7 - Default.aspx.vb

I've put a couple controls in an update panel and set the ValueSelected event of my DataTextSearch control as an AsynchPostBack trigger. So, this code fires on a partial postback and updates the Panel. Notice we can use the "Handles" syntax on the Sub that treats the selection of the item as any other bindable control action.

VB.NET
Protected Sub ColorSearchControl_ValueSelected(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles ColorSearchControl.ValueSelected
    ValueSelectedLbl.Text = "The ID of the color you chose is: " & _
    ColorSearchControl.SelectedItemID.ToString()
    TextSelectedLbl.Text = "The Text of the color you chose is: " & _
    ColorSearchControl.SelectedItemText.ToString()
    TextSelectedLbl.Style("color") = ColorSearchControl.SelectedItemText

End Sub

I have big plans for this control. It is a very powerful and flexible framework for offering users a "natural" searching experience.

History

  • 21st April, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)