Thursday, December 7, 2006

Convert Your Scraps of Script to Reusable AJAX Control Extenders

In this article, you'll see how Microsoft's AJAX Control Toolkit gives you a fantastic opportunity to repackage that JavaScript code into reusable components that can be used within the server-side ASP.NET framework, so that you easily can share your JavaScript behavior with other developers without them having to get into the guts of your JavaScript code and script libraries.

If you want to run through the steps I describe here, you'll need to download and install the ASP.NET AJAX kit, and the AJAX Control Toolkit. There is a download link at the end of the article from which you can download the complete code.

Before diving into the details of the AJAX Control Toolkit, I'd first like to share with you a situation will probably strike a familiar chord.

A couple of years ago, I had a user of our web site who complained that the standard behavior of the ASP.NET ListBox control, which by default renders as an HTML SELECT, was broken.

As far as she was concerned, when she typed a sequence of characters there should be an incremental search within the contents of the ListBox, rather than the default behavior that simply jumps to the first element that starts with each character. If you type "mi" in a ListBox, you'll end up at "IBM" rather than "Microsoft:"

I added a couple of event handlers to detect when the user gives the SELECT focus, and to detect when they typed characters within the SELECT. When the user gives the SELECT the focus, I create a DIV with a prompt that tells them that they can search, and then when they type characters, I replace the prompt message with the characters that were typed, and I select the first item that starts with the characters that were typed.

This had nothing to do with ASP.NET AJAX, or the Atlas Control Toolkit—it was just plain old JavaScript.

There are a few issues with this implementation:

  • First, whenever any I or any other developers want to use this, they'll have to hook up the JavaScript event-handlers, which can be painful when dealing with server-side controls such as the ASP.NET ListBox, which generates the SELECT.
  • Second, if you wanted to customize the behavior in some way, perhaps to use a different prompt message, or to apply a CSS style to the DIV, you'd have to edit the JavaScript. Yuck.
  • Finally, if I wanted to ensure that this behavior worked properly across all browsers then I'd have to do a lot of work—for example, different browsers have subtly different ways of hooking up event handlers.

Microsoft's ASP.NET AJAX and the Atlas Control Toolkit address all of these issues.

Of course, you probably won't have implemented this behavior exactly, but chances are you'll have written other scraps of JavaScript to do other things to make your users' lives easier. I'll show you how easy it is to repackage code into an AJAX Control Extender.
Creating an AJAX Control Extender

Once you've installed the AJAX Control Toolkit, you'll need to double-click on the AjaxControlExtender.vsi file in the AjaxControlExtender subdirectory under the kit. Doing this will install a Visual Studio Template that you can use to create AJAX Control Extenders.

If you then fire up Visual Studio 2005, and click File|New|Project, you will be presented with the option of creating a new ASP.NET AJAX Control Project. Select this option and give your project a suitable name—I've used ListSearch. Once you hit OK, you should find yourself with a project with three files:

1. ListSearchBehavior.js is where JavaScript code that actually implements your behavior will sit.
2. ListSearchDesigner.cs is used to ensure that the extender works well within the Visual Studio design-time environment. You can safely ignore this file.
3. ListSearchExtender.cs is the server-side part of the equation. This is where you can specify what kind of ASP.NET control your extender will target, and what properties your extender will expose.

The Server Side

If you start first on the server side in ListSearchExtender.cs, there were two things you want to do. I wanted to indicate what kind of ASP.NET control my extender would target, and I wanted to indicate the properties my extender exposes.

In my case, I wanted my ListSearch extender to target both ASP.NET ListBoxes and also ASP.NET DropDowns. Both of these classes derive from the ListControl class. I modified the generated class to specify the appropriate target control instead of the default, which is the top level Control class:

[Designer(typeof(ListSearchDesigner))]
[ClientScriptResource("ListSearch.ListSearchBehavior",
"ListSearch.ListSearchBehavior.js")]
[TargetControlType(typeof(ListControl))]
public class ListSearchExtender : ExtenderControlBase
{

I also wanted to indicate what properties I wanted to be available on my Extender. A couple of useful properties might be the text of the prompt message and the CSS class to be used for the prompt message. These properties will be available on the server side on the ASP.NET design surface. There is an example property created by default. I deleted it and replaced it with the two properties I wanted to use. The first was called PromptText, and looks like this:

[Description("The prompt text displayed when the user clicks the list")]
[DefaultValue("Type to search")]
[ExtenderControlProperty()]
public string PromptText
{
get
{
return GetPropertyStringValue("PromptText");
}
set
{
SetPropertyStringValue("PromptText", value);
}
}

I used the standard ComponentModel Description attribute so that the property has a description when people use it in the designer, and I marked it with the ExtenderControlProperty attribute so that the AJAX Control Toolkit knows to transfer it down to my JavaScript code.

I also indicated the default value. Please note that this is not the default value that the property will have automatically set by the AJAX Control Toolkit if the user does not specify a value in the ASP.NET designer. You'll still need to initialize this property to the default value in the JavaScript code. What the DefaultValue attribute does is tell the AJAX Control Toolkit runtime what the default value you have given the property is on the browser side, so that it knows when it doesn't need to pass the property value down to the browser—in other words, an optimization.

The implementation of the properties calls into the AJAX Control Runtime because it knows how to pass the property values over to the browser.

I also created a second property called PromptCssClass, along the same lines:

[Description("The CSS class to apply to the prompt that is displayed
when the user clicks on the list")]
[DefaultValue("")]
[ExtenderControlProperty()]
public string PromptCssClass
{
get
{
return GetPropertyStringValue("PromptCssClass");
}
set
{
SetPropertyStringValue("PromptCssClass", value);
}
}

That is all that I needed to do on the server side.