Introduction

If you’ve been following along with this article series then you know in Part 1 we talked briefly about Asynchronous JavaScript And XML or AJAX for short. We created the CallBackObject to help simplify the callback initiation process. In Part 2 we looked at integrating the CallBackObject with ASP.NET controls and events. Now, in Part 3, we are going to put all that knowledge to good use and create a custom ASP.NET control using everything we’ve learned so far. That control, as you may have guessed, is the Auto Complete TextBox (ACTB).

Before we get to the good stuff I want to say there is a lot of room for improvement for this control. Functionality, design-time integration, and ease-of-use can all be drastically improved. I leave that to you!

The Good Stuff

Before we talk about creating the control, I want to show you just how easy it is to use the control. We’ll start with Listings 1 and 2 (Actb.aspx and Actb.aspx.cs). After you calm down from your overwhelming excitement, we will examine the JavaScript portion of the ACTB Listing 3 (AutoCompleteTextBox.js) and the ASP.NET portion Listing 4 (AutoCompleteTextBox.cs).

Actb.aspx

This page is pretty straightforward. We are setting up some input fields for the user. Most of you will probably note that not all countries use a Zip code. I put this in as a demonstration of how you might customize the results of the Auto Complete TextBox based on values from other fields. (more on this later.) To use our custom control we have to register the assembly with the page and give it a tag prefix. I’ve modestly used my initials as the tag prefix but you can choose any prefix you desire. We also specify the namespace and assembly name where the control(s) can be found.

The declaration is nearly identical to that of a normal ASP TextBox. The only added attributes are the ListItemCssClass and ListItemHoverCssClass used to beautify our dropdown list. These CSS classes are defined in the external style sheet AutoCompleteTextBox.css.

Actb.aspx.cs

Things begin to get slightly more interesting when we examine the code-behind file. The first thing to note is the using statements at the top of the code:

. . .
using Microsoft.ApplicationBlocks.Data;
using WCPierce.Web;
using WCPierce.Web.UI.WebControls;

The Data Application Block provided by Microsoft is used to simplify some database access code. The other two using directives give us access to the Auto Complete TextBox and CallBackHelper classes.

Note the actbCountry_TextChanged event that we referenced in our HTML. Everything in this function is wrapped in a try/catch block and any error is returned to the client by the use of CallBackHelper.HandleError.

For the next part of the code it is important to remember that this event is firing every time the user presses a key while in the ACTB. So, if the Zip field is empty what happens is we take what the user has currently entered into the Country field, we’ll use “U” for example, and we search our database for every country that begins with the letter “U”.

AutoCompleteTextBox actb = s as AutoCompleteTextBox;
string str = String.Format("SELECT [Text]
FROM Lists
WHERE ListName='Country'
AND [Text] LIKE '{0}%'
ORDER BY [Text]", actb.Text);

All of the matching countries (Uganda, Ukraine, Uruguay, etc.) are returned in a SqlDataReader. We then set the DataSource property of the Auto Complete TextBox equal to the SqlDataReader. This process is identical to that used for binding to a DropDownList or a DataGrid.

Savvy readers will notice the only difference here is the call to BindData(). Normally with DataBound controls, you would call DataBind(). Unfortunately, I wasn’t able to make this work properly. The reason is a bit complicated but you can see why if you attempt to use an Auto Complete TextBox in a DataGrid with ViewState disabled.

That’s it! I’ve encapsulated all the hard work in the ACTB control. You can now use it just like any other data bindable control. Surf over to this site to see a demo of the ACTB in action. Leave the Zip blank, and enter a letter into the Country box and viola, you should see a drop down list with matches as well as the auto completion of the first country in the list. Sweet!

AutoCompleteTextBox.js

Before we start discussing this little gem, I need to recognize the great individuals who helped me make this control a reality:

The concepts from those two articles plus the CallBackObject yielded the ACTB. There is a lot of code in this section and rather than go over the portions covered in the two articles above, I’m only going to cover the new stuff.

Whenever a key is pressed in the ACTB, this event fires. We do some checking to see what key was pressed and if there is any data in the TextBox. If so, we make the call to Cbo.DoCallBack. As you know from Part 1, this is what initiates the server side request.

When the server side request completes, our method AutoCompleteTextBox.prototype.Cbo_Complete is executed, and we process the results.

The data returned from the server is a new-line delimited list of country names. After we remove any current entries from the drop down list, we split the return value into an array of country names. Then, we loop through the array and add each county to the drop down list, assign some events, and set any styles. The populated list is then displayed to the user, and we call the AutoSuggest method, to perform the type-ahead feature and put the first entry from the list in the TextBox and select the proper characters.

I apologize if I breezed over this portion but the articles referenced at the beginning of this section explain the remainder of the code.

AutoCompleteTextBox.cs

I tried to be thorough in my commenting of the control itself. We will cover the highlights of the control in lieu of taking you through line by line (plus my fingers are getting tired).

The first thing to note is that AutoComplete TextBox inherits from the built-in ASP TextBox:

publicclass AutoCompleteTextBox : System.Web.UI.WebControls.TextBox

This saves us a tremendous amount of work. ACTB has a number of additional properties used to specify some CSS information and the path to the JavaScript file to be used for the client side portion of things. The Render method is where the action begins:

In order for the client side JavaScript to work, it needs a reference to the TextBox that will be acting as the ACTB, as well as the <DIV> tag that will act as the drop-down portion of the control. ASP.NET controls have an ID, which you generally use in your code, and a UniqueID, which is a unique identifier for the control at the page level. Sometimes the ControlID and the UniqueID are the same, but you start running into trouble when using controls in User Controls, Server Controls, or DataList controls. So, we grab a reference to our ACTBUniqueID and create ID’s for our <DIV> tag and for use in our dynamic JavaScript code.

Next we dynamically create the JavaScript needed on the client side to create the Auto Complete TextBox. We initialize the JavaScript object with CSS properties and have ASP.NET put it in the proper place on the page for us with RegisterStartupScript.

The last few things to address are AutoPostBack and DataBind. Obviously if AutoPostBack is enabled, we defeat the purpose of AJAX. The DataBind method had to be overridden to eliminate its functionality. If one of you figure out how to make it work, please let me know. For now, developers have to call BindData instead.

Conclusion

Finally the fruits of our labor have delivered a neat little control that brings some new functionality to the ASP.NET websites. There is a lot of code to digest and I encourage you to download the source and play around. I hope I laid the ground work and planted some ideas for future development. There is a lot of potential for this technology and it is finally getting the attention it deserves.

History

2005-05-02

Initial release.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Clank,
If you are interested in an AutoComplete TextBox for VS 2005, Microsoft has a set of AJAX tools called Atlas. Within the tools and samples is an AutoComplete like textbox that puts my control to shame.

Man, your's is way better than the current atlas version!! I can actually easily customize yours to put 2 spans in the drop down div, so I can have a google results style drop down list!!

The Atlas one is sooo black box, and the javascript is all mangled so you cant easily change it!

I moved to VS 2005 and am having trouble with yours though. When it does the postback (the xmlHttp post) it gets a bunch of HTML back instead of the string list! I think it might have to do with the way the page is handling the content type? Might be due to the MasterPage? not really sure. Any ideas?

2 things that I would LOVE you for, making yours handle a web service call?
make yours more like google suggest where it caches typing while fetching data (so you can type fast without a lag)

I'm getting the same results with the example from part one and part two. The results returned have a lot of html embedded in with the intended result at the top. Not good....For some reason it thinks it needs to send the whole page back... :\

Hi Bill, this is great! I still not sure how everything is working but you got me a long way into my first .Net project. I started working with the code from this article (part 3) and had things working realy well. Then I donwloaded the new code from Lat Lays Flat - Part 2: Creating A Google Maps .NET Control to take advantage of IFrames. Now nothing works. I know that the control is not kicking off the txtChanged or it is and not the CallBack. Any Ideas?

Mr Wizerd,
Sorry to derail things for you. Hopefully we can get back on track.

The most likely culprit is the change in the required web.config entry. Rather than point directly to a file, the web.config now stores the path to a folder where all script files should reside. So, the entry should look like so:

(sorry i speak spanish) How i can cancel enter a value in the textbox that is not in the list of values???
Example list values: 'one', 'two', 'three'
If i enter 'five', the actb set text to empty, but i want to write 'five' like a correct value in the textbox.

Question:
In one of my AutoCompleteTextBox I have the OnTextChanged="get_drugName".

In the get_drugName event (which query my Db for the list)
I would like to grab the value from a RadioButtonList control (i.e user selected value) and pass it to my query, I've tried everything including:

It's possible there is a bug in the CallBackObject.js file. If you go to the link below and download the source code, you will find an updated version of the CallBackObject.js file that should work properly with checkboxes and radio buttons.

First of all, this is ONE HECK OF AN ARTICLE !! I have been searching all over the internet for a way to make the same page handle the AJAX callback, and couldnt find anything. Your article was EXTREMELY CLEAR and easy to understand ! Great Work on this !!

Now this question is not DIRECTLY concerning your control.. but its in the same area... I used your approach to great success in my project, but I have a question. I use AJAX to retrieve some search results from the database (say, a list of customers) which I then populate into a table (rendered by ASP.NET coz of an empty datagrid) using javascript. But when I postback, I lose the rows of the table. Yes, I know its a HTML table, so wont be stored in view state and modifying the table is not going to change the view state of the datagrid. So my question is... how do I handle the situation to enable my results (in the table) to persist ? I suspect I would somehow have to change the viewstate of the datagrid.. since I am populating the table from the client side using javascript. Or store the table innerHtml in a text field ... ???? I would appreciate any help you could offer to point me in the right direction. Again, GREAT WORK !!

Bill,
As I researched more, I came across the below article from msdn.. (Search for the phrase "There is one major issue that might not be obvious. ") This article does say that viewstate of controls populated using AJAX could be an issue. Any ideas on how I might be able to store the data of the tables ?

ashwinks,
Glad you liked my article. You might want to look at Ajax.Net by Michael Schwartz. His framework is superior in a number of areas with several articles demonstrating how to use it. You might be able to overcome the problems you're having with his tool.

Am returning to this after a very long time... I tried out Michael's code and yes, its very very impressive. I know you may not be able to help me directly here, but going by the very prompt replies I got from you earlier, I am wondering if you might be able to point me to someone else who might.

I could get his library to work on a standard web application, I am not able to get it to work on a sharepoint website. Many people seem to have problems using it on sharepoint. I am trying to use it in a control of mine in a subsite of sharepoint and I keep getting an error "'CustomControls' is undefined" at the line in my client script where I make the call to my Ajax enabled method. "CustomControls" is the name of my namespace of the library where my control resides. Do you think you might have some information on this ? and before you ask, yes, I have posted in almost every forum I could find that mentioned AJAX.NET... so far no luck. If you think you could help me, I will give you more detailed information on my issue.

I hope you could point me in the right direction... Thank you in any case for all the help you have extended to me thus far.

I've made a custom DropDownList that is supposed to update a normal DropDownList. I have a call to DoCallBack with the custom DDL's ID and its currently selected text (not sure if this is what I should be sending) being passed as parameters. This call is made from inside the OnChange Javascript event handler.

I've taken a look at theData, and I believe everything is ok there. After the actual CallBack has occurred, the OnSelectedIndexChanged ASP.NET event handler is envoked, and it is aware of the sender's SelectedValue. A database connection is made, and according to the debugger, the regular DDL has been populated correctly (SelectedValue shows the first item returned from the SQL query).

Like your AutoCompleteTextBox control, I have also overridden the OnSelectedIndexChanged method for the custom DDL. But the debugger shows the OnSelectedIndexChanged method that I have defined in the codebehind running twice - once by itself, and then a second time due to the custom DDL class calling base.OnSelectedIndexChanged(). So that's one issue.

The other issue is that the normal DDL isn't updated once the whole CallBack process is finished. I have noticed that the responseText (which I'm displaying in an alert box right before the call to "this.OnComplete" in ReadyStateChange) appears to be the HTML returned for the entire page, and I don't know if that's what should be returned or not.

+ When you call DoCallBack, you shouldn't need to pass the currently selected text. theData will contain the currently selected text and asp.net should see that it is different that the selected text before the call back and raise the OnSelectedIndexChanged event.

+ When you override OnSelectedIndexChanged, make sure you put if( !CallBackHelper.IsCallBack ) return; Also, I wouldn't call base.OnSelectedIndexChanged if you don't need to. This might be what is causing the responseText to contain the page HTML.

If that doesn't solve your problem, you could post/email me your code and I'll take a look at it.

Thanks for such a quick response. I'm no longer passing the selected text, and OnSelectedIndexChanged is still being raised.

Regarding overriding OnSelectedIndexChanged, did you mean to put the "if( !CallBackHelper.IsCallBack )" line in the codebehind or the actual class? I previously had the method in the class file setup like this:

robg37,
The server-side code is only half of an AJAX control. In your code you are binding some database values to your DepenedentList server-side. The client-side has no knowledge of this unless you pass back some data (via CallBackHelper.Write). For your control to work you would need to add a helper function. I copied this code posted by Lisa Leil from Part 2 of the article (you'll have to convert to C#):

Ok, with a little trickery invovling rendering the control in a new page, I got the error to stop. But now, I'm getting a "Thread was being aborted" exception during the execution of CallBackHelper.Write. Oddly, the regular DropDownList updates its content the first time I change my custom DDL, but then it doesn't on future attempts.