Extender base class features
All of the controls in the Toolkit are built upon a set of classes that really help streamline
the process of writing control extenders. These classes form
a thin "glue" layer between the Toolkit controls and the ASP.NET AJAX classes as they
exist today.
To get the most out of this document,
you should have written a control with the Toolkit, or at least read about
creating a new extender.
The Extender Classes automate a couple
of major things:
- The generation of script
that hooks up your behavior to an element on the page
- The loading of your script
file reference
- The management of any scripts
that your behavior or control may be dependent on
- The mapping of properties and
their values from the managed side to the client script
- Help in developing and debugging
your script
- A mechanism to move state back-and-forth
between your extender and the behaviors in the browser
The main class is called AjaxControlToolkit.ExtenderControlBase
(ECB for the purposes of this document). ECB is an abstract class, which
means you can't instantiate it directly; you have to create a derived type.
The main job of this component is to hook up an HTML element with some behaviors
on the page. Derived classes can add a TargetControlTypeAttribute to restrict the types
of elements they can be hooked up to.
On this class is where you declare
your managed object model. A simple one would look like this:
[TargetControlType(typeof(IButtonControl))]
public class ConfirmButtonExtender : ExtenderControlBase
{
[ExtenderControlProperty()]
public string ConfirmText
{
get
{
return GetPropertyValue("ConfirmText", "");
}
set
{
SetPropertyValue("ConfirmText", value);
}
}
}
The general lifecycle works as follows. The developer adds an extender to their ASPX page:
<ns:MyExtender ID="myExtender1"
runat="server">
TargetControlID="Button1"
SomeValue="Foo" />
When the page loads, this instantiates a server control that derives from ECB above.
The server control talks to ASP.NET AJAX to hook itself up, and the client browser automatically downloads and initializes all the necessary scripts.
ExtenderControlBase
Features:
RequiredScripts: You can specify which scripts
are required for a given extender using the RequiredScriptAttribute.
[RequiredScript(typeof(MyOtherExtender))]
public MyExtender()
{
}
This attribute takes a type that references another script file that needs to
be loaded prior to your component's scripts. This allows
you to build extenders that have behaviors that derive from or use other behaviors.
RequiredScriptAttribute also has a LoadOrder property as well to
enforce which scripts get loaded in which order. This loading is recursive as well - if the extenders you reference also have dependencies, they will automatically
be created.
[RequiredScript(typeof(MyExtender),0)]
[RequiredScript(typeof(MyOtherExtender),1)]
public MyCombinedExtender() { }
ScriptPath: This is a debugging
helper property that really simplifies your script development.
Once you've got your ECB class created, create an ASPX page.
Copy your “MyBehavior.js” file next to that ASPX page, and then add the following
(in bold) to your extender declaration:
<ns:MyExtender ID="myExtender1"
ScriptPath="MyBehavior.js"
TargetControlID="Button1"
runat="server">
</ns:<MyExtender>
This will cause the extender to load
your script from the URL you give it instead of from the DLL, which is the default.
This allows you to debug and modify your script file without having to rebuild
your extender. When you're done, don't forget to copy this file
back into your project.
BehaviorID: In cases where you would like to access the
client-side behavior for your extender from script code in the client, you can set
this BehaviorID to simplify the process. See example below:
<ns:MyExtender
ID="myExtender1"
runat="server" TargetControlID="Button1" BehaviorID="myBehavior1"
/>
<script type="text/javascript">
function changeValue() {
var myBehavior = $find("myBehavior1");
myBehavior.set_Value(0);
}
</script>
ClientScriptResourceAttribute: This isn't a method
on ECB but it's related to it. This is what associates your ECB
instance with a script file. Normally, it looks like this:
[ClientScriptResource("MyNamespace.MyBehavior",
"MyProject.MyBehavior.js")]
But if you want to wrap an existing set of scripts in ASP.NET AJAX so that you can easily use it on the server-side, you just pass
null for the namespace and drop the resource name:
[ClientScriptResource(null, "SomeScripts.js")]
ResolveTargetControlID: There are times where it is inconvenient to put the extender directly next to the control that it is extending. In cases where a NamingContainer is involved, this can prevent the extender from
being able to locate it's target control. In these cases, the user can handle the ResolveTargetControlID event on the extender. If the ECB is unable to locate the control, this event will be called with the
control's ID. In this event, the user code can locate the control and return the instance. Note that this fires for TargetControlID only, not for other properties marked with the IDPropertyAttribute. We may add that support later.
protected void MyExtender_ResolveTargetControlID(
object sender, ResolveControlEventArgs e) {
if (e.ControlID == "Button1") {
// Button1 is in another container -
// find it.
e.Control = container1.FindControl(e.ControlID);
}
}
EnsureValid: When you create
your ECB instance, you override this to make sure that the user has set all of the
appropriate values. The default implementation checks that all
of the properties marked with the RequiredPropertyAttribute are
populated.
EnableClientState: This property enables client
state transport between your extender and the class running on the client.
See the ClientState property below. This defaults
to false, so if you don't set it, ClientState will do nothing.
ClientState: This is a simple
string property that will be made available to your running behavior instance on
the client side. So you could do something like this on the server
side:
protected void
Page_Load(object sender,
EventArgs e)
{
MyExtender1.ClientState =
"hello";
}
And on the client side, (in Javascript):
var state =
this.get_ClientState(); // returns "hello"
It's that simple.
There is a corresponding set_ClientState method on the Javascript side that you can
call as well to change the value that the server-side receives.
These values persist only for the life of the page. If the user
refreshes or navigates away and back to the page, they start from their initial
state again.
Adding Extender Properties
To add extender properties to your class, you need to let the Toolkit framework
know which properties it should send to the client.
The first property is a required one. Put this property on all properties
that are related to your client-side behavior:
[ExtenderControlProperty]
public string SomeValue
{ … }
ExtenderControlPropertyAttribute: Marks a property to be
sent to the client side behavior.
Here are the optional attributes you can use on the ECB properties that you create:
DefaultValueAttribute: Put this on a TPB
property to tell the framework what the default value is for a property.
Note this does not set that value, but is a comparison.
So, of you've got a string value, you would normally put [DefaultValue(null)] or
[DefaultValue(0)] for an int. The benefit of this is that it
prevents the ECB from serializing out default values. It sounds
simple but it's pretty important to get things working right.
ClientPropertyNameAttribute:
This allows you
to declare a different property name in your managed code from your client script
code. For example, you may want to have your managed class names
start with an upper-case character, and your client script ones start with a lower
case character. Or you may be wrapping an existing behavior and
prefer other names.
[ClientPropertyName("someValue")][ExtenderControlProperty]
public string SomeValue
{ … }
IDReferenceAttribute:
This attribute tells
the ECB that it references the ID of a control on the page. So
during rendering, the ECB will replace this value with the Client ID of the control.
Note this also tells the designer what kind of controls can be set into this
property:
[IDReferenceProperty(typeof(WebControl))]
[DefaultValue("")]
[ExtenderControlProperty]
public string
PopupControlID { … }
ElementReferenceAttribute: This is related to the IDReferenceAttribute
above. This tells the framework that the property on the client side takes
an element reference rather than an ID. In this case, the framework will
automatically look up the component and pass it's value to the client instead of
the ID. This will usually be done in conjunction with the ClientPropertyNameAttribute
as in the example below:
[IDReferenceProperty(typeof(WebControl))]
[DefaultValue("")]
[ExtenderControlProperty]
[ClientPropertyName("popupElement")]
[ElementReference]
[ExtenderControlProperty]
public string
PopupControlID { … }
One last thing is on the AjaxControlToolkit.Design.ExtenderControlBaseDesigner
class. In most
cases you won't need to do anything with this class but there is one virtual member:
ExtenderPropertyName: Override this to
control the name of the property that shows up in the property grid for your extender
when you click on the item that you want to extend.
protected override
string ExtenderPropertyName
{
get
{
return "TheNameToShowInThePropertyGrid";
}
}