Cutting Edge

Customize Controls with AJAX Extenders

Dino Esposito

Code download available at: Cutting Edge 2008_01.exe(421 KB)

Contents

Getting Started
Extending a Control's Capabilities
Anatomy of an Extender Control
Input Extenders
Watermarks and Input
Confirmation and Modality
Enhanced Checkboxes
Restricted Input
Toward Better Web Pages

Input controls are essential in any application, but they're especially critical when your Web application is the face of your organization. These controls can be the primary measure by which users judge your application and even your credibility.

In both Windows® and Web programming, user interfaces are built by composing controls. But the array of controls available is not particularly rich. Input controls for Windows Forms are still based on Win32® controls, and Web controls are little more than wrappers around HTML <INPUT> tags. Clearly, better input controls are needed, especially for anyone who is writing ASP.NET AJAX applications, since these rely heavily on client-side user interaction.

In this two-part series, I'll focus on the input capabilities of ASP.NET 3.5, but most of the information I'll cover is also applicable to ASP.NET 2.0 with ASP.NET AJAX Extensions installed. I'll discuss how to enhance ASP.NET input controls using some of the JavaScript-powered behaviors available in the AJAX Control Toolkit, which I'll refer to here as ACT. This month I'll cover the basic set of ACT input extensions; next month I'll delve into more advanced features.

Getting Started

ACT is a shared-source library of Web controls that you can download from codeplex.com/AtlasControlToolkit. ACT extends existing ASP.NET server controls with predefined blocks of script code. ACT extenders can be applied to both built-in and custom ASP.NET controls.

Technically speaking, ACT components add an extra JavaScript layer on top of controls such as TextBox and Panel. Therefore, the use of AJAX is not a strict requirement. However, the implementation of ACT components relies largely on the JavaScript facilities in the Microsoft® AJAX Library as well as the ASP.NET AJAX Extensions server runtime.

ACT licensing regulations are covered at http://www.asp.net/ajaxlibrary/act.ashx. The ACT project page on CodePlex features two downloads: the ASP.NET 2.0 with AJAX Extensions and ASP.NET 3.5 (see codeplex.com/AtlasControlToolkit/Release/ProjectReleases.aspx).

To use ACT with ASP.NET applications, you need to reference the assembly in the project and then register ACT with any page that uses it. You can register ACT on a per-page basis using the @Register directive and specifying an arbitrary prefix, like so:

  <%@ Register Assembly="AjaxControlToolkit" 
               Namespace="AjaxControlToolkit" 
               TagPrefix="act" %>

Alternatively, you can register the library in the web.config file for all pages in the application. Here's the configuration script to use:

  <pages>
     <controls>
        <add tagPrefix="act" 
             namespace="AjaxControlToolkit" 
             assembly=" AjaxControlToolkit" />
     </controls>
  </pages>

ACT controls do not automatically appear in the Visual Studio toolbox, but adding a new tab is quite simple, as you can see in Figure 1.

Figure 1 ACT in Visual Studio 2008

Figure 1** ACT in Visual Studio 2008 **

Extending a Control's Capabilities

In an object-oriented framework like ASP.NET, each server control is a class that you can further customize by deriving new controls from it. For example, imagine you need a masked textbox control. You would start by creating a new MaskedTextBox class derived from the built-in TextBox class. In the derived class, you add the properties and client-side features required to deliver the kinds of behaviors needed for a masked input field. If, later on, you need a numeric textbox or an up/down input box, you can create another custom control class. But creating a new control for each additional feature is not the best idea. As Figure 2 shows, you'll end up with a proliferation of controls forming a flat hierarchy where each new control adds just a little behavior to the same base class.

Figure 2 Derived Controls versus Extenders

Figure 2** Derived Controls versus Extenders **(Click the image for a larger view)

Fortunately, there is another way to augment a control: extending the behavior of a particular instance of an existing control. In this case, the additional behavior is added via an ASP.NET extender. Any number of additional behaviors can be associated with an instance of a given control to enhance its capabilities without creating new classes.

As an ASP.NET server control, an extender gets a reference to the extended control and modifies its behavior by injecting script code. The extender control may feature a few properties of its own so developers can further configure behavior as well. The association between the target ASP.NET control and its set of extenders is typically established declaratively in the page markup. The following code snippet shows how to add a numeric up/down interface to a textbox so that users can select numbers by just clicking an up/down button:

<asp:TextBox ID="Children" runat="server" />
<act:NumericUpDownExtender ID="UpDown1" runat="server"
     TargetControlID="Children" 
     Width="100" />

As you can see, the page contains a regular TextBox control plus an instance of the NumericUpDownExtender control. The TargetControlID property on the extender sets the ID of the extendee control. The Width property is specific to the extender and defines the final width of the extendee control plus any additional UI elements contributed by the extender—in this case, a pair of up/down buttons (see Figure 3). The extender's properties can be set both declaratively and programmatically in the codebehind class.

Figure 3 NumericUpDown Extender in Action

Figure 3** NumericUpDown Extender in Action **

Anatomy of an Extender Control

ACT contains classic server controls to serve particular user interface needs and extenders to add specific behaviors to existing controls. For example, in ACT there is a TabContainer control to set up a tab-based interface as well as a ReorderList control that provides a listbox with drag-and-drop support to move items around. Mostly, though, ACT consists of extenders.

Take a look at Figure 4 where you see that an extender inherits from the base class, ExtenderControl, defined in the AJAX extensions assembly. Internally, a custom extender control class overrides the GetScriptReferences method, which returns the list of script files to be injected into the client page to implement the behavior, and the GetScriptDescriptors method, which returns the list of mappings between client-side and server-side properties. Thus any value assigned on the client to a given property will be properly reflected on the server. Aside from this relatively standard code, an extender is just JavaScript code that uses the DOM to provide richer UI elements to the end user.

Figure 4 Add Focus Capabilities to Target Controls

[
[TargetControlType(typeof(Control))]
[ClientScriptResource("IntroAjax.FocusBehavior"), "focusBehavior.js")]
public class FocusExtender : AjaxControlToolkit.ExtenderControlBase
{
    [ExtenderControlProperty]
    [RequiredProperty]
    public string HighlightCssClass
    {
        get { return GetPropertyValue("HighlightCssClass", ""); }
        set { SetPropertyValue("HighlightCssClass", value); }
    }

    [ExtenderControlProperty]
    public string NoHighlightCssClass
    {
        get { return GetPropertyValue("NoHighlightCssClass", ""); }
        set { SetPropertyValue("NoHighlightCssClass", value); }
    }
}

The sample extender featured in Figure 4 adds a focus behavior to virtually all ASP.NET controls; that is, to all controls that derive from Control. The focus feature is implemented in the linked focusbehavior.js file. This script file will likely be embedded in the assembly that contains the extender control and then downloaded to the client when requested. The focusbehavior.js file uses the programming model of the Microsoft AJAX Library (see my December 2007 column at msdn.microsoft.com/msdnmag/issues/07/12/CuttingEdge) and is centered around an initialization method that registers Document Object Model (DOM) handlers for focus and blur events. In the focus handler, the code sets the CSS class for the target element. In the blur handler, it resets the CSS class. For more details, check out the source code that accompanies this column. All things considered, the easiest way to create extenders is to use the facilities of the ACT. You run the installer in the toolkit download, set up the templates, then select "Add AJAX Control Extender" to obtain a scaffold for the extender and the behavior. More information is available at http://www.asp.net/web-forms/videos/ajax-control-toolkit/create-a-new-custom-extender.

Input Extenders

As I mentioned, ACT features several extenders that add capabilities to buttons, textboxes, panels, listboxes, and so forth. The components in ACT can be grouped in a few categories: panel, input, generic UI, animation. The full list of components with documentation and sample code can be found at asp.net/ajaxlibrary/act.ashx, and the complete list of input extenders in ACT is shown in Figure 5.

Figure 5 Input Extenders from ACT

Input Extender Target Controls Primary Goal Description
AutoComplete TextBox Improved user experience Provides a list of suggestions as the user types in the edit field.
Calendar TextBox Restricted input Provides client-side date-picking functionality with customizable date format.
ConfirmButton IButtonControl Preventing mistakes Pops up a confirmation dialog box as the user clicks the button.
FilteredTextBox TextBox Restricted input Lets users only enter text that matches a given set of characters.
HoverMenu Control Improved user experience Displays the contents of an associated panel control when the mouse hovers next to a given control—like a tooltip.
ListSearch ListControl Improved user experience Enables users to search for an item in a list by typing some of the characters.
MaskedEdit TextBox Restricted input Lets users only enter text that fits into a given input mask.
ModalPopup Control Improved user experience Implements a classic modal dialog box without using the browser's win­dow.alert method. It basically displays the contents of a panel and prevents users from interacting with the underlying page.
MutuallyExclusiveCheckBox CheckBox Restricted input Lets you define logical groups of checkboxes so that users can check only one in each group.
NumericUpDown TextBox Preventing mistakes Displays up and down buttons to select the next or previous value.
Slider TextBox Preventing mistakes Uses a slider-like user interface to select a numeric value in a range.
TextBoxWatermark TextBox Preventing mistakes Adds watermark text that disappears when the user starts typing and reappears as the textbox becomes empty.
ToggleButton CheckBox Improved user experience Enables users to display custom images to render the check buttons.
ValidatorCallout BaseValidator Improved user experience Improves the user interface of validators by popping up a balloon-style callout for each error message.

Watermarks and Input

One of the most annoying elements on a Web page is an unlabeled textbox or a page in which the labels and the textboxes don't line up. This can be caused by the font size the user chose, or other settings, but the result is that the user can't be sure just what belongs in the textbox. A great solution is a text watermark—default text in the textbox that disappears when the user begins to type in the box (see Figure 6). The following code shows how to get the effect:

Figure 6 Some Input Extenders

Figure 6** Some Input Extenders **(Click the image for a larger view)

<act:TextBoxWatermarkExtender runat="server" ID="TextBoxWatermark1"
     TargetControlID="TextBox1" 
     WatermarkText="Type Last Name Here" 
     WatermarkCssClass="watermarked" />

The extender injects script code that handles focus, blur, and keydown events. As the control gets focus, the watermark disappears. When the control loses focus, the watermark comes back if no text has been typed in. The watermark text is cleared when the page posts back. This is accomplished using a handler for the DOM's submit event. The watermark extender control allows you to specify the text and the CSS style to apply to it. You might want to use a special CSS style to visually differentiate regular input text from watermark text.

In HTML, there's one primary element for accepting data—the <input> element. You have to use it for numbers, strings, dates, currency values, and so forth. Often you need a numeric input to be restricted to a specific range of values. You can certainly enforce this business rule through a validation layer, but it would be better to do it right there in the input box.

The Slider and NumericUpDown extenders force users to enter only numbers that fall in a given range. The Slider extender hides its associated TextBox and replaces it with a graphical slider moving through discrete steps within a minimum and a maximum interval. The underlying TextBox can always be set programmatically to any value you wish. Note, though, that the assigned value must be compatible with the numeric range set via the slider; otherwise, the slider will silently fix it. Let's consider the following example:

<act:SliderExtender runat="server" ID="SliderExtender1"  
     TargetControlID="Income" 
     Minimum="0" 
     Maximum="200000" 
     Steps="41" 
     BoundControlID="IncomeAsText" />

The slider applies to a TextBox control named Income and ensures it is visually assigned a value in the range 0-200000. The Steps property indicates how many discrete steps should be provided. The preceding setting lets the slider jump by 5000 every time. If you programmatically set the slider on the server, the value is first converted to a number and then mapped to the nearest step value. If you pass in a string, the slider will ignore it and default to the initial value of the range. If you set the value of the underlying textbox on the client via JavaScript, then the value is correctly recognized over the next postback, but it is not immediately reflected by the user interface. To programmatically change the value in the slider from within the client, use the following code:

$find("SliderExtender1").set_Value(145678);

Why should you opt for $find instead of $get? The $get function is shorthand for document.getElementById. As such, it can only look for DOM elements. The $find function stands for Sys.Application.findComponent and applies to any component of the Microsoft AJAX Library that has been programmatically created. An extender is added to the page using the following script:

Sys.Application.add_init(
  function() {
      $create(AjaxControlToolkit.SliderBehavior, 
           {"BoundControlID":"IncomeAsText",
            "Maximum":200000,
            "Steps":41,
            "id":"SliderExtender1"}, 
           null, 
           null, 
           $get("Income"));
  }
);

Being a Microsoft AJAX Library component, it can only be found using the $find function.

The BoundControlID property refers to a DOM element that dynamically displays the current slider value. In most cases, you'll use a <span> or a Label for this purpose. Internally, the slider distinguishes between INPUT and any other HTML elements. It sets the value property in the former case; otherwise, it uses the innerHTML property of the matching DOM element.

The slider script hides the underlying TextBox, so the element may be flashing during the page loading. You can hide it declaratively using CSS styles. Finally, be aware that regular textboxes are displayed inline, whereas slider boxes are positioned blocks.

The NumericUpDown extender selects the next/previous value in a bound list of possible values. Despite what the name implies, NumericUpDown is not just a tool for specifying numeric quantities. The RefValues property gives you the ability to list values to move through:

<act:NumericUpDownExtender ID="UpDown1" runat="server"
     Width="120" 
     RefValues="None;1;2;3;4;5;More than 5;" 
     TargetControlID="Children" />

NumericUpDown has the ability to retrieve reference values from a remote service. The service will be invoked asynchronously each time the user clicks the up or down buttons. It's important to remember that the service must be script-callable and include methods with the following signature:

public int YourMethod(int current, string tag)  

You configure the extender to use the remote service through the ServiceDownPath, ServiceDownMethod, ServiceUpPath, and ServiceUpMethod properties. Finally, the up and down buttons don't have to be autogenerated via script. They can be buttons already defined in the page and referenced through the TargetButtonDownID and TargetButtonUpID properties.

Confirmation and Modality

Input forms that gather data to run critical server procedures are better started after the user has had an opportunity to confirm his intention. For example, it's wise to implement a confirmation step before permanently deleting some data on the server. A client-side confirmation step can only be implemented via JavaScript, by popping up something like a modal dialog box. The simplest is via the ConfirmButton extender:

<act:ConfirmButtonExtender ID="ConfirmButtonExtender1" runat="server"
     TargetControlID="Button1" 
     ConfirmText="Are you sure you want to 
                 "send this data?&#10;Really sure?" />

The extender hooks up the click event of the bound button control (not just Button, but also LinkButton and ImageButton) and pops up an alert message box. If you cancel the message, the postback won't occur. The browser's window.alert method is used to show the message. Clearly, you can code the same feature manually without the extender, but the extender is simpler and more reusable.

If you want a richer, more customized user interface, then you must look elsewhere. The ModalPopup extender is an alternative that is worth exploring. ModalPopup adds modality to a piece of markup—typically, a panel. Bound to a button control, it pops up the specified DIV tag and disables the underlying page. The element in the topmost DIV tag can't be dismissed except through the user interface it provides. The action of clicking on anything other than the element in the topmost DIV is lost and never reaches the intended target. ModalPopup performs a smart trick by adding an invisible DIV to cover the entire browser window. This layer swallows any user action and stops it from reaching underlying controls. With clever CSS coding, you can add some nice effects such as graying out anything underneath the topmost DIV (see Figure 7).

Figure 7 ModalPopup Extender

Figure 7** ModalPopup Extender **(Click the image for a larger view)

In the following code for the ModalPopup extender, the PopupControlID property is referring directly to the markup element that is to be popped up:

<act:ModalPopupExtender ID="ModalPopupExtender1" runat="server"
     TargetControlID="LinkButton1"
     PopupControlID="Panel1"
     BackgroundCssClass="modalBackground"
     OkControlID="Button1"
     OnOkScript="yes()"
     OnCancelScript="no()"
     CancelControlID="Button2" />

In the majority of cases, this element tends to be an ASP.NET panel control or a plain DIV element. The BackgroundCssClass property indicates the CSS style to apply to the underlying page for the duration of the modal dialog. In order to get the grayed-out effect you see in Figure 7, you should use the following attributes:

.modalBackground 
{
    background-color:Gray;
    filter:alpha(opacity=70);
    opacity:0.7;
}

Nearly all modal dialog boxes have a pair of OK and Cancel buttons. These buttons can be link, push, or image buttons and can be labeled with any text. You use the OkControlID and CancelControlID properties to tell the extender which controls in the panel represent the OK and Cancel buttons. Likewise, you use OnOkScript and OnCancelScript properties to indicate JavaScript functions to be run when the user clicks to confirm or dismiss the modal dialog box.

Enhanced Checkboxes

Another extender that can help users to enter correct data and save the system some extra client validation steps is MutuallyExclusiveCheckBox. It lets you define logical groups of checkboxes such that users can check only one in each group:

<asp:CheckBox runat="server" ID="beginner" Text="Beginner" />
<asp:CheckBox runat="server" ID="intermed" Text="Intermediate" />
<act:MutuallyExclusiveCheckBoxExtender runat="server" ID="Mutual1"
     TargetControlID="beginner"
     Key="Expertise" />
<act:MutuallyExclusiveCheckBoxExtender runat="server" ID="Mutual2"
     TargetControlID="intermediate"
     Key="Expertise" />

The preceding extenders define a group of two checkboxes named Expertise that are mutually exclusive. Normally, you would use checkboxes to allow multiple choices and use radio buttons to force the user to choose just one option. So why not use a RadioButtonList control here? Well, as you may have noticed, a RadioButtonList control can have all options unselected initially, but once a selection has been made there's no way to return to a fully unselected state. In other words, you can unselect an item only by selecting another one, and one is always selected. A group of checkboxes using MutuallyExclusiveCheckbox extenders does not have this limitation.

Checkboxes are usually displayed using bitmaps embedded in the browser. Changing the bitmap programmatically is not easy because there's no exposed public property. The ToggleButton extender hides the bound checkbox element and replaces it with a piece of dynamically generated markup that emulates the layout of a checkbox except that it uses a user-defined pair of bitmaps for the checked and unchecked states:

act:ToggleButtonExtender ID="ToggleButtonExtender1" runat="server"
    TargetControlID="CheckBox1" 
    ImageHeight="19"
    ImageWidth="19"
    UncheckedImageUrl="~/images/DontLike.gif" 
    CheckedImageUrl="~/images/Like.gif" />

The ToggleButton extender cannot be applied to a CheckBoxList control; it only works with individual checkboxes. It's nice to have the ability to change the look of the standard checkbox.

Restricted Input

Preventing user mistakes involves controlling the input by filtering out undesired characters, invalid expressions, or data of the wrong type. Using the standard TextBox control, you would enforce proper input by validating it on the server where you could convert the string to any other data type. With AJAX, input data types are more controlled. For example, by using the calendar extender you make it virtually impossible for users to type anything other than a date. ASP.NET comes with a server Calendar control, but a calendar extender is really different because it builds its user interface entirely on the client, works entirely on the client, and generates no postbacks at all as the user navigates to find month and day. Furthermore, if a given browser doesn't support JavaScript, the old textbox is displayed:

<act:CalendarExtender ID="CalendarExtender1" runat="server"
     TargetControlID="Birth"
     Format="dd/MM/yyyy" />

The preceding code snippet is sufficient to display a popup calendar, as the associated textbox receives the focus. As an alternative, you can display the popup when the user clicks a page button. The ID of the button is set through the PopupButtonID property. The Format property indicates the format of the date as it will be written to the textbox when the user dismisses the calendar popup (see Figure 8).

Figure 8 Calendar Extender

Figure 8** Calendar Extender **

The FilteredTextBox extender prevents a user from entering invalid characters into a textbox. It basically adds JavaScript that hooks up the keyboard activity and filters out undesired keystrokes. You can configure the extender to refuse certain characters or to ensure that only specified characters are accepted. You use the FilterMode property for this setting. It determines the type of filter to apply—only numbers, only lowercase or uppercase, and a custom set of characters. It should be noted that a TextBox with a watermark can't be filtered to accept numeric values only. The filter layer, in fact, will automatically clear any watermark text you set.

Toward Better Web Pages

Extenders are a powerful and smart way to add extra behaviors to ASP.NET controls. ACT comes with more than 30 extenders to enrich built-in controls. In ACT, you find extenders to add animation, drag-and-drop functionality, autocompletion, and, more importantly, enhance input capabilities. ACT can be downloaded with or without the original source code. Additionally, most complex extenders have cross-dependencies. For example, the Calendar extender requires the Popup extender.

In this column, I've reviewed the simplest and most commonly used extenders that can make your pages more effective. In the next column, I'll go on with more advanced functionality such as masked editing and autocompletion.

Send your questions and comments for Dino to cutting@microsoft.com.

Dino Esposito is a mentor at Solid Quality Learning and the author of Introducing ASP.NET AJAX (Microsoft Press, 2007). Based in Italy, Dino is a frequent speaker at industry events worldwide. You can reach him at cutting@microsoft.com or join his blog at weblogs.asp.net/despos.