Creating an Extender Control to Associate a Client Behavior with a Web Server Control
AJAX functionality in ASP.NET enables you to expand the capabilities of a Web application in order to create a rich user experience. You can use the ECMAScript (JavaScript), DHTML, and AJAX capabilities of the Web browser to include visual effects, client processing such as validation, and so on.
This is an advanced tutorial for those who wish to create custom controls and/or add Ajax capabilities to a control. To view the available controls that already have Ajax capabilities, see the ASP.NET AJAX Control Toolkit.
This tutorial shows you how to create an extender control that encapsulates a client behavior and links it to a Web server control. The client behavior adds functionality to the browser's Document Object Model (DOM) elements. The extender control is then associated with one or more types of ASP.NET server controls to add the behavior to those server controls. You can associate more than one extender control with an ASP.NET server control.
In this tutorial you will learn how to do the following:
Create an extender control that encapsulates client behavior and that is attached to Web server controls on an ASP.NET Web page.
Create a client behavior that is associated with the Web server extender control.
Handle events from the browser DOM by using the client behavior.
Note
You can also add rich client capabilities to ASP.NET server controls without a separate extender control. For an example of how to create a Web server control that includes the same client capability that is shown in this tutorial, see Adding Client Capabilities to a Web Server Control.
Compile the custom extender control into an assembly and embed associated JavaScript files as resources in the same assembly.
Reference the compiled custom extender control in an ASP.NET AJAX-enabled Web page.
A Visual Studio project with source code is available to accompany this topic: Download.
Identifying the Client Requirements
This tutorial implements a simple client behavior that highlights a control in a Web page (such as a TextBox or Button control) when the control is selected (or has focus) in the browser. For example, the control might change background color when it has focus, and then return to the default color when focus moves to another control.
To implement this behavior, the client control in this tutorial requires the capabilities that are listed in the following table.
Required Capability |
Implementation |
---|---|
A way to highlight a DOM element. |
To highlight a DOM element in an ASP.NET Web page, the client control applies a cascading style sheet (CSS) style, which is identified by a class name. This style is user configurable. |
A way to return the DOM element to its non-highlighted state. |
To remove the highlight from a DOM element in an ASP.NET page, the client control applies a CSS style, which is identified by a class name. This style is user configurable and is applied to the DOM element as the default style. |
A way to identify when a DOM element is selected. |
To identify when a DOM element is selected (has focus), the control handles the onfocus event of the DOM element. |
A way to identify when a DOM element is not selected. |
To identify when a control is no longer selected, the control handles the onblur event of the DOM element. |
Creating the Extender Control
To encapsulate the client behavior for use by ASP.NET page developers, you can use an extender control. An extender control is a Web server control that inherits the ExtenderControl abstract class in the System.Web.UI namespace. Extender controls can be applied to specific Web server control types. You identify the types of Web server controls to which an extender control can be applied by using the TargetControlTypeAttribute attribute.
The extender control in this tutorial can be applied to any kind of Web server control. The following example shows the class definition.
<TargetControlType(GetType(Control))> _
Public Class FocusExtender
Inherits ExtenderControl
[TargetControlType(typeof(Control))]
public class FocusExtender : ExtenderControl
The new extender control includes two properties that are used to implement the client requirements:
HighlightCssClass, which identifies the CSS class that will be applied to the DOM element to highlight the control when it has focus.
NoHighlightCssClass, which identifies the CSS class that will be applied to the DOM element when it does not have focus.
Inheriting the ExtenderControl Abstract Class
The following table lists members of the ExtenderControl abstract class that you must implement in an extender control.
Member |
Description |
---|---|
Returns a collection of ScriptDescriptor objects that represent ECMAScript (JavaScript) client components. This includes the client type to create, the properties to assign, and the events to add handlers for. |
|
Returns a collection of ScriptReference objects that contain information about the client-script libraries to be included with the control. The client-script libraries define the client types and include any other JavaScript code that is required for the control. |
The extender control in this tutorial uses the GetScriptDescriptors() method to define the instance of the client behavior type. The control creates a new ScriptBehaviorDescriptor object (the ScriptBehaviorDescriptor class derives from the ScriptDescriptor class) and includes the object in the return value for the GetScriptDescriptors method.
The ScriptBehaviorDescriptor object includes the name of the client class (Samples.FocusBehavior) and the ClientID value for the associated (target) Web server control. The client class name and the ClientID property values are supplied to the constructor for the ScriptBehaviorDescriptor object. A reference to the target Web server control is supplied as a parameter to the GetScriptDescriptors(Control) method. The reference can be used to determine the ClientID value of the target Web server control, which is the id value for the rendered DOM element.
The ScriptBehaviorDescriptor class is used to set the client behavior's property values, which are obtained from properties of the extender control on the server. To define the client behavior's properties, the extender control uses the AddProperty method of the ScriptBehaviorDescriptor class. The extender control then specifies the name and value for the property of the client behavior, based on the corresponding property of the server extender control. This example uses a ScriptBehaviorDescriptor object to set the values for the highlightCssClass and nohighlightCssClass properties in the client behavior.
The extender control supplies the ScriptBehaviorDescriptor object in the return value for the GetScriptDescriptors method. Therefore, whenever the Web server control is rendered to the browser, ASP.NET renders JavaScript that creates an instance of the client behavior with all defined properties and event handlers. The behavior instance is attached to the DOM element, based on the ClientID property that is rendered from the target Web server control. The following example shows declarative ASP.NET markup that includes an ASP.NET server control and the extender control from this tutorial in a page.
<asp:TextBox ID="TextBox1" runat="server" />
<sample: FocusExtender runat="server"
ID="FocusExtender1"
HighlightCssClass="MyHighLight"
NoHighlightCssClass="MyLowLight"
TargetControlID="TextBox1" />
The rendered output of the page includes a call to the $create method that identifies the client behavior to create. It also provides values for the client behavior's properties and the id value of the DOM element that the client behavior targets. The following example shows a rendered $create method.
$create(Samples.FocusBehavior, {"highlightCssClass":"MyHighLight","nohighlightCssClass":"MyLowLight"}, null, null, $get('TextBox1'));
The extender control in this tutorial uses the GetScriptReferences method to pass the location of the script library that defines the client behavior type. In the example, this is a URL to the script file named FocusBehavior.js, which is created later in this tutorial. The reference is made by creating a new ScriptReference object, and then setting the Path property to the URL of the file that contains the client code.
The following example shows the implementations of the GetScriptDescriptors and GetScriptReferences methods.
Protected Overrides Function GetScriptReferences() As IEnumerable(Of ScriptReference)
Dim reference As ScriptReference = New ScriptReference()
reference.Path = ResolveClientUrl("FocusBehavior.js")
Return New ScriptReference() {reference}
End Function
Protected Overrides Function GetScriptDescriptors(ByVal targetControl As Control) As IEnumerable(Of ScriptDescriptor)
Dim descriptor As ScriptBehaviorDescriptor = New ScriptBehaviorDescriptor("Samples.FocusBehavior", targetControl.ClientID)
descriptor.AddProperty("highlightCssClass", Me.HighlightCssClass)
descriptor.AddProperty("nohighlightCssClass", Me.NoHighlightCssClass)
Return New ScriptDescriptor() {descriptor}
End Function
protected override IEnumerable<ScriptReference> GetScriptReferences()
{
ScriptReference reference = new ScriptReference();
reference.Path = ResolveClientUrl("FocusBehavior.js");
return new ScriptReference[] { reference };
}
protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors(Control targetControl)
{
ScriptBehaviorDescriptor descriptor = new ScriptBehaviorDescriptor("Samples.FocusBehavior", targetControl.ClientID);
descriptor.AddProperty("highlightCssClass", this.HighlightCssClass);
descriptor.AddProperty("nohighlightCssClass", this.NoHighlightCssClass);
return new ScriptDescriptor[] { descriptor };
}
Creating the Client Behavior
In the extender control, the GetScriptReferences method specifies a JavaScript file (FocusBehavior.js) that contains the client code for the behavior type. This section describes the JavaScript code in that file.
The client behavior code matches the members that were specified in the ScriptDescriptor objects that are returned by the GetScriptDescriptors method. A client behavior can also have members that do not correspond to members in the server extender control.
The extender control in this tutorial sets the name of the client behavior to Samples.FocusBehavior, and it defines two properties of the client behavior, highlightCssClass and nohighlightCssClass.
For more information about how to create client components and behaviors, see Creating a Client Component Class Using the Prototype Model.
Creating the Client Namespace
The client code must first call the registerNamespace method of the Type class to create its namespace (Samples). The following example shows how to register the client namespace.
// Register the namespace for the control.
Type.registerNamespace('Samples');
Defining the Client Class
The Samples.FocusBehavior class defines the Samples.FocusBehavior client class. It includes two properties to hold the property values supplied by the Web server control.
Defining the Class Prototype
After the Samples.FocusBehavior class is defined, the client code defines the prototype for the class. The prototype includes property get and set accessors, and event handlers for the onfocus and onblur events of the DOM element. It also includes an initialize method that is called when an instance of the behavior is created, and a dispose method that performs cleanup when the behavior is no longer required by the page.
Defining the Event Handlers for the DOM Element
Event handlers for a client class are defined as methods of the class prototype. The handlers are associated with event delegates and with events of the browser DOM by using the addHandlers method, which is discussed later in this topic with the initialize method.
Defining the Property Get and Set Methods
Each property identified in the ScriptDescriptor object of the extender control's GetScriptDescriptors method must have corresponding client accessors. The client property accessors are defined as get_<property name> and set_<property name> methods of the client class prototype.
Implementing the Initialize and Dispose Methods
The initialize method is called when an instance of the behavior is created. Use this method to set default property values, to create function delegates, and to add delegates as event handlers.
The initialize method of the Samples.FocusBehavior class does the following:
Calls the initialize method of the Sys.UI.Behavior base class.
Calls the addHandlers method to add event delegates as handlers for the onfocus and onblur events of the associated DOM element. Note that the "on" part of the event name (for example, onfocus) is not specified.
The dispose method is called when an instance of the behavior is no longer used on the page and is removed. Use this method to free any resources that are no longer required for the behavior, such as DOM event handlers.
The dispose method of the Sample.FocusBehavior class does the following:
Calls the clearHandlers method to clear the event delegates as handlers for the onfocus and onblur events of the associated DOM element.
Calls the dispose method of the Behavior base class.
Note
The dispose method of a client class might be called more than one time. Make sure that the code you include in the dispose method takes this into account.
Registering the Behavior
The final task in creating the client behavior is to register the client class by calling the registerClass method. Because the class is a client behavior, the call to the registerClass method includes the JavaScript class name to register. It also specifies Behavior as the base class.
The complete example below includes a call to the notifyScriptLoaded method of the Sys.Application class. This call is required in order to notify the Microsoft AJAX Library that the JavaScript file has been loaded.
The following example shows the complete JavaScript code for the Samples.FocusBehavior client behavior. The code in this tutorial requires the JavaScript file to be named FocusBehavior.js and to be put in the Scripts directory.
// Register the namespace for the control.
Type.registerNamespace('Samples');
//
// Define the behavior properties.
//
Samples.FocusBehavior = function(element) {
Samples.FocusBehavior.initializeBase(this, [element]);
this._highlightCssClass = null;
this._nohighlightCssClass = null;
}
//
// Create the prototype for the behavior.
//
Samples.FocusBehavior.prototype = {
initialize : function() {
Samples.FocusBehavior.callBaseMethod(this, 'initialize');
$addHandlers(this.get_element(),
{ 'focus' : this._onFocus,
'blur' : this._onBlur },
this);
this.get_element().className = this._nohighlightCssClass;
},
dispose : function() {
$clearHandlers(this.get_element());
Samples.FocusBehavior.callBaseMethod(this, 'dispose');
},
//
// Event delegates
//
_onFocus : function(e) {
if (this.get_element() && !this.get_element().disabled) {
this.get_element().className = this._highlightCssClass;
}
},
_onBlur : function(e) {
if (this.get_element() && !this.get_element().disabled) {
this.get_element().className = this._nohighlightCssClass;
}
},
//
// Behavior properties
//
get_highlightCssClass : function() {
return this._highlightCssClass;
},
set_highlightCssClass : function(value) {
if (this._highlightCssClass !== value) {
this._highlightCssClass = value;
this.raisePropertyChanged('highlightCssClass');
}
},
get_nohighlightCssClass : function() {
return this._nohighlightCssClass;
},
set_nohighlightCssClass : function(value) {
if (this._nohighlightCssClass !== value) {
this._nohighlightCssClass = value;
this.raisePropertyChanged('nohighlightCssClass');
}
}
}
// Optional descriptor for JSON serialization.
Samples.FocusBehavior.descriptor = {
properties: [ {name: 'highlightCssClass', type: String},
{name: 'nohighlightCssClass', type: String} ]
}
// Register the class as a type that inherits from Sys.UI.Control.
Samples.FocusBehavior.registerClass('Samples.FocusBehavior', Sys.UI.Behavior);
if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
The following example shows the complete code for the ASP.NET page.
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>ASP.NET AJAX Behavior Sample</title>
<style type="text/css">
.LowLight
{
background-color:#EEEEEE;
}
.HighLight
{
background-color:#FFFFF0;
}
.LowLightButton
{
font-weight:normal;
width:100px;
}
.HighLightButton
{
font-weight:bold;
width:100px;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<div>
<table border="0" cellpadding="2">
<tr>
<td><asp:Label runat="server" ID="Label1" AssociatedControlID="TextBox1">Name</asp:Label></td>
<td><asp:TextBox ID="TextBox1" runat="server" /></td>
</tr>
<tr>
<td><asp:Label runat="server" ID="Label2" AssociatedControlID="TextBox2">Phone</asp:Label></td>
<td><asp:TextBox ID="TextBox2" runat="server" /></td>
</tr>
<tr>
<td><asp:Label runat="server" ID="Label3" AssociatedControlID="TextBox3">E-mail</asp:Label></td>
<td><asp:TextBox ID="TextBox3" runat="server" /></td>
</tr>
</table>
<asp:Button runat="server" ID="Button1" Text="Submit Form" />
<sample:FocusExtender ID="FocusExtender1" runat="server"
NoHighlightCssClass="LowLight"
HighlightCssClass="HighLight"
TargetControlID="TextBox1" />
<sample:FocusExtender ID="FocusExtender2" runat="server"
NoHighlightCssClass="LowLight"
HighlightCssClass="HighLight"
TargetControlID="TextBox2" />
<sample:FocusExtender ID="FocusExtender3" runat="server"
NoHighlightCssClass="LowLight"
HighlightCssClass="HighLight"
TargetControlID="TextBox3" />
<sample:FocusExtender ID="FocusExtender4" runat="server"
NoHighlightCssClass="LowLightButton"
HighlightCssClass="HighLightButton"
TargetControlID="Button1" />
</div>
</form>
</body>
</html>
The following example shows the complete code for the FocusExtender class. This code is normally put in the App_Code directory.
Imports System
Imports System.Data
Imports System.Configuration
Imports System.Web
Imports System.Web.Security
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web.UI.WebControls.WebParts
Imports System.Web.UI.HtmlControls
Imports System.Collections.Generic
Namespace Samples.VB
<TargetControlType(GetType(Control))> _
Public Class FocusExtender
Inherits ExtenderControl
Private _highlightCssClass As String
Private _noHighlightCssClass As String
Public Property HighlightCssClass() As String
Get
Return _highlightCssClass
End Get
Set(ByVal value As String)
_highlightCssClass = value
End Set
End Property
Public Property NoHighlightCssClass() As String
Get
Return _noHighlightCssClass
End Get
Set(ByVal value As String)
_noHighlightCssClass = value
End Set
End Property
Protected Overrides Function GetScriptReferences() As IEnumerable(Of ScriptReference)
Dim reference As ScriptReference = New ScriptReference()
reference.Path = ResolveClientUrl("FocusBehavior.js")
Return New ScriptReference() {reference}
End Function
Protected Overrides Function GetScriptDescriptors(ByVal targetControl As Control) As IEnumerable(Of ScriptDescriptor)
Dim descriptor As ScriptBehaviorDescriptor = New ScriptBehaviorDescriptor("Samples.FocusBehavior", targetControl.ClientID)
descriptor.AddProperty("highlightCssClass", Me.HighlightCssClass)
descriptor.AddProperty("nohighlightCssClass", Me.NoHighlightCssClass)
Return New ScriptDescriptor() {descriptor}
End Function
End Class
End Namespace
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections.Generic;
namespace Samples.CS
{
[TargetControlType(typeof(Control))]
public class FocusExtender : ExtenderControl
{
private string _highlightCssClass;
private string _noHighlightCssClass;
public string HighlightCssClass
{
get { return _highlightCssClass; }
set { _highlightCssClass = value; }
}
public string NoHighlightCssClass
{
get { return _noHighlightCssClass; }
set { _noHighlightCssClass = value; }
}
protected override IEnumerable<ScriptReference> GetScriptReferences()
{
ScriptReference reference = new ScriptReference();
reference.Path = ResolveClientUrl("FocusBehavior.js");
return new ScriptReference[] { reference };
}
protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors(Control targetControl)
{
ScriptBehaviorDescriptor descriptor = new ScriptBehaviorDescriptor("Samples.FocusBehavior", targetControl.ClientID);
descriptor.AddProperty("highlightCssClass", this.HighlightCssClass);
descriptor.AddProperty("nohighlightCssClass", this.NoHighlightCssClass);
return new ScriptDescriptor[] { descriptor };
}
}
}
Dynamically Compiling the Extender Control for Testing
Any Web server control, such as the extender control in this tutorial, must be compiled before you can reference it in a Web page. You can use the dynamic compilation feature of ASP.NET version 2.0 to test Web server controls without manually compiling the controls into an assembly. This saves time when you are initially writing and debugging the Web server control code. The following steps show you how to use the App_Code folder to dynamically compile your extender control.
To put the extender control in the App_Code folder for dynamic compilation
Create an App_Code folder under the root folder of the Web site.
Move the .cs or .vb control source files and any related classes into the App_Code folder.
-or-
If you previously added an assembly for the control to the Bin folder, delete the assembly. You continue to edit the source files in the App_Code folder. The control source code will be compiled every time that you run your project.
Note
You can either compile a control into an assembly and place the assembly in the Bin folder, or you can place the control's source file in the App_Code folder, but you cannot do both. If you add the control to both folders, the page parser will not be able to resolve a reference to the control in a page and will raise an error.
Run the Web page. The extender control is dynamically compiled.
Testing the Dynamically Compiled Extender Control in a Web Page
The following procedure describes how to test the extender control in an ASP.NET AJAX-enabled Web page. The code for the Web server control is compiled dynamically from the App_Code folder.
To use the behavior in an ASP.NET page
Create a new ASP.NET Web page.
If the page does not already have a ScriptManager control, add one.
Create CSS style rules for text boxes that are highlighted and for the text boxes that are not highlighted.
You can highlight the control any way that you like. For example, you can change the control's background color, add a border, or change the font of text.
Add an @ Register directive to the page, and then specify the namespace and the TagPrefix attribute for the extender control,
Note
In this example, the server control code is in the App_Code folder so that it can be dynamically compiled. Therefore, an assembly attribute is not specified.
Add a TextBox and a Button control to the page and set their Id properties.
The markup for the controls must include runat="server".
Add an instance of the FocusExtender control to the page.
Set the TargetControlID property of the FocusExtender control to the ID of the Button control that you added previously.
Set the HighlightCssClass property to the highlight CSS style, and set the NoHighlightCssClass property to the no highlight CSS style.
Run the page and select each control.
Notice that when you select the Button control, it is highlighted.
Change the TargetControlID property of the FocusExtender control to the ID of the TextBox control, and then run the page again.
This time, the TextBox control is highlighted when it has focus. The behavior encapsulated in the FocusExtender control can be applied to different ASP.NET server controls on the page. If you want the behavior to apply to multiple controls, you can add multiple instances of the extender control to the page and associate each instance with a different ASP.NET server control.
Compiling the Extender Control into an Assembly
Embedding the JavaScript component and Web server control's extension code into an assembly will make your custom extender control easier to deploy. Creating an assembly also makes it easier to manage version control for the control. In addition, controls cannot be added to the toolbox of a designer unless they are compiled into an assembly.
To compile the extender control into an assembly
In the Properties window for the FocusBehavior.js file, set Build Action to Embedded Resource.
Add the following property to the AssemblyInfo file.
<Assembly: System.Web.UI.WebResource("Samples.FocusBehavior.js", "text/javascript")>
[assembly: System.Web.UI.WebResource("Samples.FocusBehavior.js", "text/javascript")]
Note
The AssemblyInfo.vb file is in the My Project node of Solution Explorer. If you do not see any files in the My Project node, do the following: on the Project menu, click Show All Files. The AssemblyInfo.cs file is in the Properties node of Solution Explorer.
The WebResource definition for JavaScript files must follow a naming convention of [assembly namespace].[JavaScript File name].js.
Note
By default, Visual Studio sets the assembly namespace to the assembly name. You can edit the assembly namespace in the assembly's properties.
In the FocusExtender class file, change the ScriptReference object in the GetScriptReferences method to reference the client control script that is embedded in the Samples assembly. To do so, make the following changes:
Replace the Path property with a Assembly property set to "Samples".
Add a Name property and set its value to "Samples.FocusBehavior.js".
The following example shows the result of this change.
Protected Overrides Function GetScriptReferences() As IEnumerable(Of ScriptReference) Dim reference As ScriptReference = New ScriptReference() reference.Assembly = "Samples" reference.Name = "Samples.FocusBehavior.js" Return New ScriptReference() {reference} End Function
protected override IEnumerable<ScriptReference> GetScriptReferences() { ScriptReference reference = new ScriptReference(); reference.Assembly = "Samples"; reference.Name = "Samples.FocusBehavior.js"; return new ScriptReference[] { reference }; }
Build the project.
When compilation finishes, you will have an assembly named Samples.dll. The JavaScript code file (FocusBehavior.js) is embedded in this assembly as a resource.
Note
Remember to rebuild the class library project any time that you add new source files or change existing ones.
Using the Compiled Extender Control from its Assembly in a Web Page
You will now reference the compiled custom extender control in an ASP.NET AJAX-enabled Web page.
To reference the custom extender control in an ASP.NET AJAX-enabled Web page
Create a new ASP.NET AJAX project.
In the root directory of the Web site, create a Bin folder.
Copy the Samples.dll assembly from the Bin\Debug or Bin\Release folder of the Samples class project to the new Bin folder.
Add a new ASP.NET Web page named TestFocusExtender.aspx, and then add the following markup to the new page.
<%@ Register Assembly="Samples" Namespace="Samples.VB" TagPrefix="sample" %>
<%@ Register Assembly="Samples" Namespace="Samples.CS" TagPrefix="sample" %>
Because the server control is compiled into an assembly, the @ Register directive has an Assembly attribute that references the Samples assembly in addition to the Namespace and TagPrefix attributes.
Run the page and select each control.
When you select the FocusBehavior control, it is highlighted.
The Web page that uses the compiled custom extender control includes the Assembly attribute in the @ Register directive. Otherwise, it is the same as the Web page you used for the control in the App_Code folder.
See Also
Concepts
Adding Client Capabilities to a Web Server Control
Using the ASP.NET UpdatePanel Control with Data-Bound Controls