Click here to Skip to main content
Click here to Skip to main content

Building a Better AutoComplete User Control

, 21 Apr 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
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:

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:
    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.
    Select Case ServiceMethod
        Case ServiceMethods.Colors
            autoComplete1.ServiceMethod = "GetColorsByName"
        Case ServiceMethods.States
            autoComplete1.ServiceMethod = "GetStatesByName"
    End Select

    Here is the Public Enum:

    Public Enum ServiceMethods
        Colors = 1
        States = 2
    End Enum
  • Turn Highlighting On or Off and configure the WaterMarkText according to the UserControl configuration:
    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.
    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:
    autoComplete1.OnClientItemSelected = _
    	"ControlClickSubmit_" & autoComplete1.ClientID
  • Register the script you created on the page:

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

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

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

    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)

Share

About the Author

Steve Krile

United States United States
I have been coding web applications since 2001. Starting with classic .asp and rolling from there, I have a passion for user interface design and building zero-training applications.

Comments and Discussions

 
GeneralMy vote of 5 Pinmembermanoj kumar choubey3-Feb-12 20:42 
QuestionCan get this to work in Visual web developer PinmemberSpider89907-Aug-10 2:03 
Generalnot working [modified] PinmemberYASSER_KARAKI6-Nov-09 1:22 
AnswerBug Fix -- Say you want to use two or more of these on a page, then submit for processing with another button (not autopostback) Pinmemberjamanley5-Jun-09 6:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141220.1 | Last Updated 21 Apr 2009
Article Copyright 2009 by Steve Krile
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid