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.
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_
(
" 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