Contents |
· 1 Introduction · 2 Control or Extender? · 3 Component Options · 4 Description Process · 5 Referencing Process · 6 AJAX Control Toolkit · 7 JavaScript Made Easier · 8 Conclusion |
If you haven’t read Part 1 and 2 of this series, I highly recommend reading those articles first. This article extends the features explained in the first two articles, illustrating the client concepts applied to custom control or extender development. As I mentioned before, ASP.NET AJAX takes a managed approach to developing JavaScript code. The ASP.NET control/extender architecture leverages this managed approach to create a class definition (and any accompanying enumerations, interfaces, etc.) to expose a client-side API.
ASP.NET AJAX framework Series
· Part 1 Overview of the client portion of the ASP.NET AJAX framework.
· Part 2 Overview of the client portion of the ASP.NET AJAX framework.
· Part 3 Overview of the client portion of the ASP.NET AJAX framework.
· Part 4 Overview of the server portion of the ASP.NET AJAX framework.
· Part 5 Overview of rendering the UI of a control with ASP.NET AJAX.
I refer to the client-side class created using a constructor and prototype, then registered using the registerClass method, as the client component. The class that the control or extender uses on the server side is referred to the server component. Both a client and server component makes up an AJAX control or extender.
So what is the difference between a control and extender? As you may know with custom ASP.NET controls, a custom server control renders its own user interface. What this means is that at runtime when the rendering of the control occurs, the interface renders HTML markup to the browser. The AJAX client component that is associated with this control extends the control that the server component renders to the browser. It does this through the ClientID property, and is accessible on the client-side through the get_element() property getter.
Extenders, on the other hand, use the client component script to extend another control that it targets. This is established through the TargetControlID property. Extenders don’t render their own markup to the browser, but exists solely to extend the control that it targets. The scripts that the extender emits also target the control provided through the TargetControlID attribute.
The two approaches are mostly identical. When using the ASP.NET AJAX or the AJAX Control Toolkit approach to describe components and referencing scripts, both the extender and control development approaches are similar enough to each other that they may seem to blend in this article.
The approach that the ASP.NET AJAX framework takes for developing AJAX controls or extenders is to create a C# or VB.NET class, and associate a JavaScript class to it. This “partnership” creates both a server and client API that can be taken advantage of in ASP.NET pages. Along with this partnership come a few new conventions that are helpful to use. I detail these options in Part 1 of the article in this series; please read this before continuing because it is expected that you are familiar with these conventions.
There are a few development options depending on your needs. Not only is there an option created by the ASP.NET AJAX framework, the AJAX control toolkit also created a new approach to control/extender development that’s pretty unique. The options for developing AJAX controls are:
· System.Web.UI.IScriptControl server interface, Sys.UI.Control client class – At the bare minimum, IScriptControl defines the interface that an AJAX control can implement, which the ASP.NET AJAX framework can take advantage of. This interface should be used where you need to inherit from another base class than the ones listed below.
· System.Web.UI.ScriptControl server control, Sys.UI.Control client class – If you don’t need to inherit from a different class, have your custom control inherit from ScriptControl. This base class implements IScriptControl and performs some registrations that are required in IScriptControl implementations.
· AjaxControlToolkit.ScriptControlBase server control, AjaxControlToolkit.ControlBase client class – The AJAX control toolkit approach is a very unique approach, using attributes to perform dynamic description and registration of the component (more on this soon). Its client counterpart makes certain properties available, like client state for storing AJAX properties.
Extenders have a very similar architecture; the architecture has the same capabilities, so I’ll list them without explaining them in detail.
· System.Web.UI.IExtenderControl server interface and Sys.UI.Behavior client class
· System.Web.UI.ExtenderControl server control and Sys.UI.Behavior client class
· AjaxControlToolkit.ExtenderControlBase server control and AjaxControlToolkit.BehaviorBase client class
See how the two “lines” of components are very similar in setup? The main difference between the two, mentioned above, is that the script line of controls emit their own user interface, where the extender line of controls do not emit an interface and target another control.
A process of describing the component must take place. Every AJAX component in the ASP.NET AJAX framework describes its component, stating all of the events and properties it exposes. An initial value for properties and an event handler for events are passed along from the server to the client.
For AJAX controls and extenders, server properties typically have a client counterpart. The client component is actually exposing a client-side API that ASP.NET pages can make use of, which is the purpose of why the server-side properties have a client-side equivalent. This client-side API gives AJAX components a powerful addition to custom control development.
The properties and events that a component uses on the client have to be described in the GetScriptDescriptors method implemented by the IScriptControl interface. GetScriptDescriptors returns an array of ScriptDescriptor objects that contain the descriptions of the objects; however, usually only one descriptor object is needed.
Take a look at the following example:
Listing 1: Script Description Process
01.public System.Collections.Generic.IEnumerable<ScriptDescriptor> GetScriptDescriptors()
02.{
03. ScriptBehaviorDescriptor descriptor = new
04. ScriptBehaviorDescriptor("Nucleo.Web.ListControls.ClientCheckboxList",
05. this.ClientID);
06. descriptor.AddProperty("activeIndex", this.ActiveIndex);
07. descriptor.AddProperty("selectedIndex", this.SelectedIndex);
08. descriptor.AddElementProperty("newItemsClientState", this.NewItemClientStateID);
09. descriptor.AddElementProperty("removedItemsClientState",
10. this.RemovedItemClientStateID);
11. descriptor.AddEvent("allItemsSelected", this.OnClientAllItemsSelected);
12. descriptor.AddEvent("noItemsSelected", this.OnClientNoItemsSelected);
13. descriptor.AddEvent("itemSelected", this.OnClientItemSelected);
14. descriptor.AddEvent("itemToggled", this.OnClientItemToggled);
15. return new ScriptDescriptor[] { descriptor };
16.}
Notice a few things:
· The ScriptDescriptor object is abstract, and there are five implementations. The implementation I’m using is ScriptBehaviorDescriptor.
· The constructor of ScriptBehaviorDescriptor takes the full name of the client class and the unique identifier of the current control.
· Properties are added using the AddProperty method. This method takes the name of the client property (lower camel case in this instance) and passes along the current value assigned on the server side.
· The AddElementProperty is a unique way to get a reference to the HTML element in your client script. These two properties are actual references to a HiddenField control (actually the client <input> element). This saves you from having to pass over the ID and use $get() to obtain a reference; AddElementProperty does that for you.
· The AddComponentProperty (not shown above) is another helpful property that works like AddElementProperty, except it works with AJAX components. It’s a shortcut for passing over the ID of the element and using $find() to obtain the component reference.
· Events are defined in property statements. This means that the property reads/writes the name of an event handler (defined in the page or a script registered with the ScriptManager) for a client event.
Not only do scripts need describing, the ASP.NET AJAX framework needs to know how to deploy them. This deployment process could be disconnected from the DLL that the AJAX control or extender lives in; however, the recommended approach is to embed the script in the assembly of the project. To do this, select the JavaScript file in the Visual Studio solution explorer. Click the down arrow for the build action, and select embedded resource.
Figure 1: Embedding a script in a Visual Studio project
Embedding the script is the first step; the next step requires setting this script up as a web resource. The following statement registers a JavaScript file as a web resource. Notice the long name of the JavaScript file.
Listing 2: The WebResource attribute
1.[assembly:
2.System.Web.UI.WebResource("Nucleo.Web.ButtonControls.ButtonVisibilityExtender.js",
3. "text/javascript")]
The interesting part about this is that the file name is only ButtonVisibilityExtender.js. When referencing resources, the full name of the resource identifies that resource. In my example, Nucleo is the default namespace, and the script is in the Web/ButtonControls folder (as C# takes folder names to be part of the namespace).
This embedded resource is traditionally retrieved using the following statement. Web Resource URLs use a GUID-like definition to retrieve information from the DLL.
Listing 3: Getting a web resource URL, and seeing the results from the browser
1.Page.ClientScript.GetWebResourceUrl(this.GetType(),
2. "Nucleo.Web.ListControls.ClientCheckboxList.js");
1.//<script src="/ScriptResource.axd?d=f-
2.N_jD_D2iaHL1Ka2MVBcf8fH872qh7oYukHate78_P288cQrN0qf9JnycaAW8B7jOAhF5R4XfD6FrwgeSzkk-
3.Cm4u33dNjSdlj2ehWZV6k1&t=633518952268140000" type="text/javascript"></script>
So we can see that the .NET framework has a means to extract these scripts from a DLL, using a unique key. This is the last part of the referencing process, which maps the script to the HTML element that the AJAX control emits or the AJAX extender targets.
The process I just explained is how simple the registration process is; the ASP.NET AJAX framework requires you to reference your scripts using the GetScriptReferences method; this method returns an enumerable list of ScriptReference objects. However, usually one suffices. Take a look at the following definition of one:
Listing 4: GetScriptReferences definition
1.public IEnumerable<ScriptReference> GetScriptReferences()
2.{
3. ScriptReference reference = new ScriptReference();
4.if (this.Page != null)
5. reference.Path = Page.ClientScript.GetWebResourceUrl(this.GetType(),
6. “fully.quantified.script.name.js”);
7. return new ScriptReference[] { reference };
8.}
The AJAX control toolkit came up with a brand new way to develop scripts. This process uses attributes to identify the properties, methods, and events that need registration. This process makes AJAX control/extender development easier. No longer do you need to implement the GetScriptDescriptors and GetScriptReferences methods; this process happens dynamically at runtime, using the information provided in the class/property/method attributes.
In a way, this makes it easier to develop applications; however, you have to be aware of all the nuances that are possible with the AJAX control toolkit way of developing applications. The AJAX Control Toolkit may make use of existing .NET framework attributes that it looks for, but you wouldn’t know it without consulting the documentation (if it is documented).
Let’s look at the process of describing a component using the AJAX control toolkit. Below is an event definition and a property definition:
Listing 5: Listing 5: Property and Event definition
01.[ExtenderControlEvent, ClientPropertyName("blur")]
02.public string OnClientBlur
03.{
04. get { return (string)(ViewState["OnClientBlur"] ?? null); }
05. set { ViewState["OnClientBlur"] = value; }
06.}
07.[
08.ClientPropertyName("roundedDigits"), ExtenderControlProperty
09.]
10.public int RoundedDigits
11.{
12. get { return (int)(ViewState["RoundedDigits"] ?? -1); }
13. set { ViewState["RoundedDigits"] = value; }
14.}
Notice that the difference between properties and events is through a single attribute: ExtenderControlProperty or ExtenderControlEvent. Remember in Listing 1 I showed an example of a ScriptDescriptor object describing properties and events using AddProperty and AddEvent method, respectively. With the AJAX Control Toolkit approach, you don’t need to add properties or events; the attribute-based information is reflected upon and the description process occurs automatically. There isn’t a need to override GetScriptDescriptors, unless you are doing something special in your component.
How about the script reference process? Again, the AJAX control toolkit uses attributes to identify the files it needs. Using the same process for embedding scripts in a DLL as shown in Figure 1 and referencing the file as in Listing 2, the next part of the process is to identify where that script is loaded using the ClientScriptResource attribute.
Listing 6: AJAX Control Toolkit Component shell
01.[assembly:
02.System.Web.UI.WebResource("Nucleo.Web.ButtonControls.BaseButtonExtender.js",
03. "text/javascript")]
04.namespace Nucleo.Web.ButtonControls
05.{
06. [
07. TargetControlType(typeof(IButtonControl)),
08. ClientScriptResource("Nucleo.Web.ButtonControls.BaseButtonExtender",
09. "Nucleo.Web.ButtonControls.BaseButtonExtender.js")
10. ]
11. public class BaseButtonExtender : ExtenderControlBase
12. {
13. }
14.}
Listing 6 puts the script referencing process altogether for this custom extender. ClientScriptResource references the name of the client component being used and the location to the embedded script using its full resource name.
JavaScript knowledge of scripting is required for an AJAX component to be successful. Not only should you be aware of some of the existing constructs and options of developing JavaScript, but also of the ASP.NET AJAX framework and AJAX Control Toolkit. Both frameworks add on to what’s already there in JavaScript, and make it easy to perform common tasks. What I mean by all of this is not to go out and study JavaScript and memorize every single method, but that as you develop script components, you will find yourself looking for more and more JavaScript resources online, in forums, and in books. This is because JavaScript, though more manageable and extended, still is the cornerstone of this framework’s rich UI capabilities.
The AJAX Control Toolkit has a CommonToolkitScripts object that has several helper methods that you may find useful whenever developing custom components.
· getLocation – Gets the point location of the an HTML element.
· setLocation – Sets the location of an HTML element to the values provided by a point object. A point object has x and y properties for reference.
· getContentSize and getSize – Gets the size of the content or outer dimensions of an HTML element, returning an object that has the width and height.
· setContentSize and setSize – Sets the size of the content or outer dimensions of an HTML element. This method takes a size object that has width and height properties.
· getMargin and getPadding – Gets the margin and padding values for an HTML element.
· parseUnit – Parses a string into a unit.
· getElementOpacity – Gets the current opacity of an element, checking if any filters have been established for the element.
· getVisible – Gets whether the current element has its visibility set to hidden or display set to none.
· setVisible – Sets whether the current element is visible.
The list does go on and there isn’t enough room to talk about all of them. To find out more information about these methods, download the AJAX Control Toolkit source code from http://www.codeplex.com/ and check out the AjaxControlToolkit\Common\Common.js file. This file has the complete code for all of these helper methods.
As you can see, JavaScript is becoming more managed, and there are a lot of helper utilities to make JavaScript development easier, so there is less hassle in developing these AJAX components. Furthermore, we looked at the inner workings of creating custom AJAX controls and extenders, which is essential to know if you plan to create a custom AJAX component yourself.