Skip navigation

Reuse, Recycle, Extend

Create an AJAX Extender Using the AJAX Control Toolkit

CoverStory

LANGUAGES: C# | VB.NET

ASP.NET VERSIONS: 3.5

 

Reuse, Recycle, Extend

Create an AJAX Extender Using the AJAX Control Toolkit

 

By Brian Mains

 

You ve likely seen quite a few options if you ve looked for JavaScript libraries on the Internet. The idea behind a JavaScript library is to make JavaScript development easier. After all, developing applications solely through AJAX requires 100 times more code than a conventional ASP.NET application.

 

ASP.NET AJAX is a JavaScript library that uses what s been done in the .NET Framework, and structures itself enough to mimic the .NET Framework. Because the JavaScript framework doesn t support some of the conventions that the .NET Framework uses, there are some workarounds required to put these conventions in place. Although it is expected you are familiar with these conventions, I ll try to touch on them as I go.

 

The example in this article creates an AJAX extender. An extender control does not render its own user interface; by that I mean that extender controls emit no user interface at rendering time. An extender targets another control through its TargetControlID property; the targeted control is the recipient of the client JavaScript code emitted by the extender component. This is the process of extending existing .NET controls.

 

Whether the object is an AJAX control or extender, there are two pieces that are consistently present: a server component and a client component. A server component, which is a control that resides in a class library, defines the control portion of the extender. This server component can be shown in the Visual Studio toolbox and dragged to an ASP.NET page. The server component works in tandem with a client component. This client component exists in a .js file, and implements the ASP.NET AJAX definition of classes (illustrated later in the article).

 

Let s look at some of the options available with extenders. There are some options when it comes to developing custom AJAX extenders. Initially, the ASP.NET AJAX Framework created the System.Web.UI.ExtenderControl server component. The extender control requires the developer to implement the GetScriptDescriptors and GetScriptReferences methods. These two methods are responsible for describing the properties, methods, and events of the client component and specifying where the JavaScript file resides, respectively.

 

This extender control has a client-counterpart named Sys.UI.Behavior, which has all of the client-side features that are equally exposed on the server side. This component inherits from Sys.Component and exposes the base set of properties, methods, and events needed for working with extenders.

 

When the AJAX Control Toolkit was released, another option was present: AjaxControlToolkit.ScriptControlBase. This server component uses a completely different approach to developing AJAX components. Overall, I think it s much better than its predecessor; however, it takes some getting used to. This approach uses attributes to specify the location of the script and the description of the component. Previously, with the ExtenderControl base class, the developer implemented the description/registration in the GetScriptDescriptors and GetScriptReferences methods; using the AJAX Control Toolkit approach, these methods are rarely used or needed.

 

Let s take a look at the options the AJAX Control Toolkit provides:

  • AjaxControlToolkit.ExtenderControlBase. The toolkit base class for server extender components. ExtenderControlBase inherits from the System.Web.UI.ExtenderControl class and extends on what properties or methods are available.
  • AjaxControlToolkit.BehaviorBase. The base class for client extender components. Every server component has a client component counterpart that builds on the previous architecture.

 

Before we get into the example code, let s take a look at the structure of the component.

 

AJAX Control Toolkit Structure

As mentioned, every server control or extender must describe the client component. With the AJAX Control Toolkit development approach, this happens through the use of attributes. At run time, the ExtenderControlBase base class reflects on the properties and methods of the component and examines their attributes for specific attribute types. If the attributes are AJAX Control Toolkit attributes, the script dynamically creates the descriptor based on its information. Let s take a look at an example of describing a property:

 

[

ExtenderControlProperty,

ClientPropertyName("calculatorControlID"),

IDReferenceProperty

]

public string CalculatorControlID { .. }

 

This property defines three attributes:

  • ExtenderControlProperty. This specifies that the property above maps to a property in the client component. The value of the property will be assigned to the client component to be used as its initial (and possibly only) value.
  • ClientPropertyName. This contains the name of the property on the client component. If omitted, the name of the property is used in the exact case instead. Normally, JavaScript uses lower-camel case, where C# and VB.NET classes use upper-camel case, and therefore ClientPropertyName can be used to handle that difference.
  • IDReferenceProperty. This specifies that the field contains the ID of another control; at run time, this ID will be translated into the ClientID of the underlying HTML element. Finding the ID value can be difficult sometimes, especially with the use of master pages. Even though every control on a page may have a unique ID, at run time, the .NET Framework gives each control a unique ID stored in both the ClientID and UniqueID properties. There are a couple of options when using IDs, which I ll discuss later.

 

Any attribute can be specified; however, only certain attributes matter to the AJAX Control Toolkit process. Let s look at an example of an event definition:

 

[

ClientPropertyName("totalUpdated"),

ExtenderControlEvent

]

public string OnClientTotalUpdated { .. }

 

Notice an event definition is defined as a property. An event definition uses a property value to read/write the name of an event handler (essentially the JavaScript method name). The ExtenderControlEvent attribute signals at run time that the property is supplying an event handler definition. The ClientPropertyName attribute specifies that the event s client name is the totalUpdated event. If OnClientTotalUpdated has the name of the JavaScript method Component_TotalUpdated, this method name is supplied as an event handler to the totalUpdated event. We ll see how this works later. Let s now look at how the script gets registered, which occurs at the class level (see Figure 1).

 

[assembly: System.Web.UI.WebResource(

"Nucleo.Web.MathControls.CalculatedFieldExtender.js",

"text/javascript")]

namespace Nucleo.Web.MathControls

{

  [

 TargetControlType(typeof(ITextControl)),

 ClientScriptResource(

"Nucleo.Web.MathControls.CalculatedFieldExtender",

"Nucleo.Web.MathControls.CalculatedFieldExtender.js")

 ]

 public class CalculatedFieldExtender

: ExtenderControlBase

 {

 }

}

Figure 1: Script registration using an embedded resource.

 

I mentioned earlier that scripts get registered through the GetScriptReferences method. In the ASP.NET AJAX Framework, the script points to the location of the JavaScript file containing the client component code. ASP.NET AJAX deploys scripts by storing them as an embedded resource and retrieving them through the Page.ClientScript.GetWebResourceUrl method. The AJAX Control Toolkit automates this by specifying the name of the component and file in the ClientScriptResource attribute. The name of the JavaScript file is not the actual file name, but the resource name that the .NET Framework gives it (the fully quantified resource name).

 

Component Description

As I scour the Web and look at forum posts, I occasionally see questions about the ability to perform summation of controls using an AJAX approach. This can be seen as a calculator-type effect, where the first x number of textboxes in a row or column are the input values, and the final textbox or label is the summed result.

 

In typical JavaScript, the approach to handle this is to attach an onkeypress or onkeyup handler to the textboxes. When the textbox fires its keyup event, all the textboxes on the page are summed and the final summation is displayed in a textbox or label. This occurs as the user changes the value by a single key press, displaying an immediate calculated sum to the user. Although initially the summed value is incorrect because most of the values have not yet been provided, as the user enters more numbers into the textboxes, the total amount becomes apparent.

 

I wanted to create reusable logic though, and not stick to the ordinary approach. Even though achievable through ordinary JavaScript, the example I created for this article uses extenders that target textbox controls as the source and detects any changes. When the value changes as numbers are entered, the extender passes this value to another extender, whose responsibility is to sum the values.

 

This means that the CalculatedFieldExtender, which we ll see soon, targets a textbox control, as well as maintains a reference to the CalculatorExtender that sums the values. When its value changes through a key press, this change in values notifies the CalculatorExtender, and this extender receives the update request through an event.

 

For the CalculatorExtender to know what the final value is, it gets a reference to all the CalculatedFieldExtender extenders that point to it (because there can be more than one CalculatorExtender instance). Once it has a reference to these extenders, it needs to loop through the extenders, get the current value, and calculate the output value.

 

Calculating Individual Fields

Let s start by looking at the definition of the CalculatedFieldExtender, whose responsibility is to map to the textbox and pass along its value. The extender inherits from ExtenderControlBase and defines some attributes about the script. Let s look at the shell of the server component (see Figure 2).

 

[assembly: System.Web.UI.WebResource(

"Nucleo.Web.MathControls.CalculatedFieldExtender.js",

"text/javascript")]

namespace Nucleo.Web.MathControls

{

  [

 TargetControlType(typeof(ITextControl)),

 ClientScriptResource(

"Nucleo.Web.MathControls.CalculatedFieldExtender",

"Nucleo.Web.MathControls.CalculatedFieldExtender.js")

 ]

 public class CalculatedFieldExtender

: ExtenderControlBase

 {

 }

}

Figure 2: The CalculatedFieldExtender server component shell.

 

I explained the process of registering scripts for Figure 1, so I won t explain it again. The extender exposes two properties (shown in Figure 3): one stores the summation control s ID; the second stores the name of a JavaScript method that listens for the fieldValueChanged event.

 

[

ClientPropertyName("calculatorControlID"),

ExtenderControlProperty,

IDReferenceProperty(typeof(CalculatorExtender)),

RequiredProperty

]

public string CalculatorControlID

{

 get { return (string)(ViewState["CalculatorControlID"]

?? null); }

 set { ViewState["CalculatorControlID"] = value; }

}

[

ClientPropertyName("fieldValueChanged"),

ExtenderControlEvent

]

public string OnClientFieldValueChanged

{

 get { return (string)

(ViewState["OnClientFieldValueChanged"] ?? null); }

 set { ViewState["OnClientFieldValueChanged"] = value; }

}

Figure 3: The server component s properties.

 

Notice the two properties only differ slightly, but represent two completely different purposes. Both use lower-camel case as their client name, and retain their values in ViewState. The event definition, by convention, prefixes the event name with OnClient . The only major difference is that one property defines the ExtenderControlProperty attribute, and one defines the ExtenderControlEvent attribute.

 

In our example, these property values are pushed down to the client. The value supplied to the CalculatorControlID is supplied to the calculatorControlID client property (or the set_calculatorControlID property setter). The OnClientFieldValueChanged property value is pushed down to the add_fieldStatusChanged method, which registers the defined method for that event (if the method isn t null). We ll get to this in a bit; however, I want to move next into the structure of the client component. Figure 4 contains the shell of the client component.

 

Type.registerNamespace("Nucleo.Web.MathControls");

//***************************************************************

// Constructor

//***************************************************************

Nucleo.Web.MathControls.CalculatedFieldExtender

= function(associatedElement)

{

   Nucleo.Web.MathControls.CalculatedFieldExtender

.initializeBase(this, [associatedElement]);

}

Nucleo.Web.MathControls.CalculatedFieldExtender.prototype =

{

}

Nucleo.Web.MathControls.CalculatedFieldExtender.descriptor

= {

 properties: [ .. ],

 events: [ .. ]

}

Nucleo.Web.MathControls.CalculatedFieldExtender

.registerClass(

'Nucleo.Web.MathControls.CalculatedFieldExtender',

AjaxControlToolkit.BehaviorBase);

if (typeof(Sys) !== 'undefined')

 Sys.Application.notifyScriptLoaded();

Figure 4: The client component shell.

 

There are four main sections to this script:

  • Constructor definition. When working with AJAX controls or extenders, a one-parameter constructor is required. This parameter is the HTML element being rendered or extended. The extender or control handles accessing this object for you automatically. For the base class to work correctly, the initializeBase method passes the associatedElement in array form up to the base class.
  • Prototype definition. The properties, methods, events, etc. for the client component are defined in the prototype, as we ll see soon.
  • Descriptor definition. Describes the component, listing the properties, events, methods, etc. that the component exposes.
  • Script registration. Class registration is required so the ASP.NET AJAX Framework knows of the class existence, in addition to the base class and interfaces it implements. Script notification is also required to ensure that the application knows that it has loaded.

 

I m going to discuss the first two, which are the most relevant to the topic at hand.

 

Constructor Definition

Figure 5 shows the definition of the constructor. The constructor performs two functions. First, it passes the associated HTML element to the base class through initializeBase. This HTML element is later available using the element property (accessible through the getter, get_element). Second, it defines all the variables used in the script. Variables should be declared in the constructor (for performance reasons).

 

Nucleo.Web.MathControls.CalculatedFieldExtender =

   function(associatedElement)

{

 Nucleo.Web.MathControls.CalculatedFieldExtender.initializeBase(

       this, [associatedElement]);

 

 this._currentValue = null;

 this._calculatorControlID = null;

 this._keyPressHandler = null;

 this._keyUpHandler = null;

}

Figure 5: The client component constructor.

 

Prototype Definition

The prototype is the body of the class. Whenever an instance is created of that class type, the prototype defines the members of the instance. The syntax changes a little bit; instead of defining the function keyword at the beginning, it shifts it to the end (as shown in Figure 6).

 

Nucleo.Web.MathControls.CalculatedFieldExtender.prototype =

{

      get_calculatorControlID : function()

      {

   return this._calculatorControlID;

      },

      set_calculatorControlID : function(value)

      {

   if (this._calculatorControlID != value)

   {

     this._calculatorControlID = value;

     this.raisePropertyChanged("calculatorControlID");

   }

      },

      add_fieldValueChanged : function(handler)

      {

    this.get_events().addHandler('fieldValueChanged',

 handler);

      },

      remove_fieldValueChanged : function(handler)

      {

this.get_events().removeHandler('fieldValueChanged',

handler);

      },

      raise_fieldValueChanged : function(e)

      {

   var eventHandler = this.get_events().getHandler(

'fieldValueChanged');

 

   if (eventHandler != null)

     eventHandler(this, e);

      }

}

Figure 6: Properties and events.

 

Previously I mentioned that the server component pushes values up to the client. The CalculatorControlID property pushed the client ID of the CalculatorExtender to the set_calculatorControlID property setter, and the OnClientFieldStatusChanged property pushes the JavaScript method name to the add_fieldValueChanged method. These members are shown in Figure 6.

 

The code in Figure 6 is pretty straightforward; properties have getters and setters noted by the get_ and set_ prefixes. Events have methods to add and remove handlers using the add_ and remove_ prefix. Events also can be raised using the raise_ or _on prefix. Property setters only change the underlying variable value when there is a difference in values; the property changed event is raised when this happens.

 

The prototype contains two other important lifecycle methods: initialize and dispose. As you can imagine, initialize fires at the beginning of the lifecycle process (before the client init event) and dispose fires at the end. It is in the initialize method that the text control s key press event is mapped to a client method, which checks for key presses and passes this notification to the calculator.

 

In VB.NET, the structure to add an event handler for an event is a two-step process. First, a method with a matching event signature is defined in the code. The second part of the process is to map this method as an event handler for an event of a class through the AddHandler method.

 

On the client side, the process is similar (again, through a two-step process). First, a new add-on to the Function object creates a delegate to the event handler through the createDelegate method. This method returns a handler that points to the event handler. The second step is to use the $addHandler static method, which maps the HTML element s event to this delegate. This two step process is shown in Figure 7. Note that the initialize and dispose methods are also defined in the prototype.

 

initialize : function()

{

 Nucleo.Web.MathControls.CalculatedFieldExtender

.callBaseMethod(this, "initialize");

 

 this._keyPressHandler = Function.createDelegate(this,

this.keyPressCallback);

 $addHandler(this.get_element(), 'keypress',

this._keyPressHandler);

 this._keyUpHandler = Function.createDelegate(this,

this.keyUpCallback);

 $addHandler(this.get_element(), 'keyup',

this._keyUpHandler);

}

Figure 7: Initializing the event handlers.

 

The call to callBaseMethod calls the initialize method on the base class; you may wonder what the difference is between initializeBase and callBaseMethod. The initializeBase method is used for the constructor only; everywhere else, callBaseMethod should be used (which invokes a method call on the base class definition).

 

In the initialize method, the keyPressCallback and keyUpCallback methods fire when a key is pressed in a targeted control (see Figure 8). The value in the text control is evaluated to ensure it really is a number using another new function: parseInvariant. If really a number, that value is stored in the _currentValue property. If the value is not a number (the NaN constant), it is set to zero.

 

keyUpCallback : function(domEvent)

{

 var value = Number.parseInvariant(

this.get_element().value);

 

 if (value != NaN)

 {

   if (this._currentValue != value)

   {

     this._currentValue = value;

     this.raise_fieldValueChanged(Sys.EventArgs.Empty);

   }

 }

 else

 {

   if (this._currentValue != 0)

   {

     this._currentValue = 0;

     this.raise_fieldValueChanged(Sys.EventArgs.Empty);

   }

 }

},

Figure 8: Recalculating through key up.

 

It is at this point, when the key is depressed, that the fieldValueChanged event fires; whenever the value changes, any subscriber to the event (who called the add_fieldValueChanged method) gets a notification of that event. This notification is important because the CalculatorExtender, which sums up the values, needs to know when an extender s targeted control has its value changed. The CalculatorExtender uses this event to recalculate its total and supply the new value to the control.

 

Let s think about this a little more. Suppose a form has six input fields that take numeric values. As the user types numbers into the textboxes, for every key press an event fires (fieldValueChanged). This event is actually sending a notification to the second companion component, letting that component know that it needs to recalculate itself.

 

Suppose the user selects the first textbox, then presses the 2 key. The calculator extender now shows the value 2 . As the user keeps typing numbers, say 5 and 0 , the calculator shows 250 . The user tabs to the next textbox, and enters 5 , 0 , 0 . The calculator now shows 750. This process continues for every textbox that has a CalculatedFieldExtender control mapped to it. Now it s important to move on and understand how the calculation happens.

 

The CalculatorExtender Component

The two components explained in this article are similar to each other. They both use an embedded script and define the type of control it extends. However, one of the two differences is the TargetControlType attribute; to make the calculator flexible, any control can be extended to show the total (see Figure 9).

 

[

TargetControlType(typeof(Control)),

ClientScriptResource(

"Nucleo.Web.MathControls.CalculatorExtender",

"Nucleo.Web.MathControls.CalculatorExtender.js")

]

public class CalculatorExtender : ExtenderControlBase

{  [

 ClientPropertyName("controlValueUnassigned"),

 ExtenderControlEvent

 ]

 public string OnClientControlValueUnassigned

 {

   get { return base.GetPropertyValue(

      "OnClientControlValueUnassigned", null); }

   set { base.SetPropertyValue(

      "OnClientControlValueUnassigned", value); }

 }

       [

 ClientPropertyName("totalUpdated"),

 ExtenderControlEvent

 ]

 public string OnClientTotalUpdated

 {

   get { return base.GetPropertyValue(

      "OnClientTotalUpdated", null); }

   set { base.SetPropertyValue(

      "OnClientTotalUpdated", value); }

 }

       [

 ClientPropertyName("roundedDigits"),

 ExtenderControlProperty

 ]

 public int RoundedDigits

 {

   get { return (int)(ViewState["RoundedDigits"] ?? -1); }

   set { ViewState["RoundedDigits"] = value; }

 }

  [

 ClientPropertyName("roundValue"),

 ExtenderControlProperty

 ]

 public bool RoundValue

 {

   get { return (bool)(ViewState["RoundValue"]

?? false); }

   set { ViewState["RoundValue"] = value; }

 }

}

Figure 9: CalculatorExtender definition.

 

The other difference with this script is the use of the GetPropertyValue and SetPropertyValue generic methods; these methods are helper methods (defined in ExtenderControlBase) to work with ViewState. Notice everywhere else I use the null coalesce operator (??), but in this case I used these helper methods to read/write to ViewState.

 

As a bonus, the CalculatorExtender keeps a collection of CalculatedFieldExtenders that map to this extender. Remember, the CalculatedFieldExtender has a reference to the CalculatorExtender with which it works. This process works because the CalculatedFieldExtender calls the RegisterField method on the CalculatorExtender to register itself with this control. Figure 10 shows the code of the CalculatorExtender s RegisterField method and the CalculatedFieldExtender s OnInit event handler.

 

//Defined in CalculatorExtender

internal void RegisterField(CalculatedFieldExtender field)

{

 if (field == null)

   throw new ArgumentNullException("field");

 this.Fields.Add(field);

}

//Defined in CalculatedFieldExtender

protected override void OnInit(EventArgs e)

{

 base.OnInit(e);

 CalculatorExtender calculator = this.FindControlHelper(

this.CalculatorControlID) as CalculatorExtender;

 if (calculator == null)

   throw new ArgumentException(string.Format(

"The calculator with an id of '{0}' could not be found.",

this.CalculatorControlID));

 calculator.RegisterField(this);

}

Figure 10: Registering CalculatedFieldExtender controls.

 

On initialization, the CalculatedFieldExtender uses its CalculatorControlID property to obtain a reference to the calculator. FindControlHelper is another useful method in the AJAX Control Toolkit s ExtenderControlBase class, which loops iteratively through the control collection looking for the desired extender control (by its ID). So at initialization, each of the field extenders registers itself with the calculator, so the calculator knows with which extenders it is working. Rather than storing this in ViewState, the collection recreates itself every page load at initialization time.

 

The same process also happens on the client side in pretty much the same way. The _registerCalculatedField is called by the CalculatedFieldExtender client component so the CalculatorExtender knows which extenders it needs to sum up using JavaScript code. Figure 11 shows the definition, as well as another important property.

 

_registerCalculatedField : function(extender)

{

 if (extender == null)

   throw Error.argumentNull("extender", "Extender is null");

 extender.add_fieldValueChanged(this.fieldValueChangedCallback);

 

 Array.add(this.get_extenders(), extender);

 this.calculate();

},

get_extenders : function()

{

 if (this._extenders == null)

   this._extenders = [];

 return this._extenders;

},

Figure 11: Registration of extenders on the client side.

 

The list of extenders is stored in an array on the client side. It s more important to have the list of extenders on the client side, because this is where the calculation happens. The server-side listing is also handy, but not required like it is on the client.

 

The list of extenders is exposed through a getter; the calculator can simply loop iteratively through this list to calculate their current values and post the summation. The calculator knows that the value changes through the fieldValueChangedCallback callback method that fires every time a key is pressed in the underlying textbox. Notice in the _registerCalculatedField method that the calculator component registers to the fieldValueChanged event so it knows a recalculation is in order.

 

This recalculation process is the core of the work, and it happens in both the calculate and _setControlTotal methods. These methods are responsible for looping through the extenders, extracting the total values from each individual control, and summing its values, kicking out the total value to the targeted control. The two places where this method is called is during the initialization process and every time the fieldValueChanged event fires.

 

The process of displaying the final value may be easy if the CalculatorExtender had control over the user interface; however, to add flexibility, the extender tries to interrogate what the final control representation is (because, remember, the CalculatorExtender targets objects of the control type). Let s look at the definition, and I ll further explain the key parts (see Figure 12).

 

calculate : function

{

 if (this.get_extenders() == null ||

this.get_extenders().length == 0)

 {

   this._totalValue = 0;

   this._setControlTotal();

   return;

 }

 var total = 0;

 for (var i = 0; i < this.get_extenders().length; i++)

 {

   var extender = this.get_extenders()[i];

   total += extender.get_currentValue();

 }

 

 this._totalValue = total;

 this._setControlTotal();   

},

_setControlTotal : function()

{

 var element = this.get_element();

 //Try assigning the value to the value property

 if (element.value != null && element.value != undefined)

   element.value = this._totalValue;

 //Otherwise, raise an event to notify

 //that the value has been unassigned

 else

   this.raise_controlValueUnassigned();

}

Figure 12: Refreshing the summation.

 

If the extender s collection is blank, the summation result is zero; this is important, because if it simply exited without resetting the value, the previous total would still appear incorrectly (at client time, extenders can be dynamically added or removed). When there are extenders registered on the client side, each of these extenders has a currentValue property (defined as get_currentValue) that is interrogated iteratively. You may think it is unsafe to assume the current value is in its correct form; after all, the value could be text or some other type. However, I ve taken an extra step in the CalculatedFieldExtender to ensure that the currentValue property stores a legitimate numeric value.

 

I ve removed the code that manipulates the UI to a private method simply because this code is reused in multiple places. The _setControlTotal method s responsibility is to simply work with the user interface; it checks to see if the HTML element being targeted has a value property.

 

For extra flexibility, an event is raised if the element can t be assigned a value (because it doesn t know what to assign the total to). This forces the ASPX page to set the total. I could have checked the existence of an innerText property, as well; however, I didn t want to do that because it might not be the correct action (innerText may exist, but may be an inappropriate display for some controls). In the future, I may make this configurable, but for now the controlValueUnassigned event signals to the consumer that the total value could not be assigned to the underlying extended control, and therefore it is the page s responsibility to make that assignment.

 

User Interface Demo

I ve set up a simple form that uses six textboxes and a label. Each textbox has a CalculatedFieldExtender mapped to it. The extender taps in to the keyup event, checking for changes. Once the value changes, if it is a valid number, that number is passed to the CalculatorExtender, which loops through each of the extenders on the client side and queries its currentValue property. If the current value of that textbox is not a numerical value, the value is ignored in the calculation.

 

The total result appears in the label at the bottom, mapped to a CalculatorExtender. The label does not have a value property, but uses the innerText property on the client to make the assignment (because it is a span element).

 

Figure 13 shows an example of the markup for the form. As you can see, it s not difficult to set up this example, and it s flexible because TextBox/CalculatedFieldExtender controls can be added repeatedly to this page without any client-side code changes.

 

 

 

   

     

     

     

   

   

     

     

     

   

   

     

     

     

   

   

     

     

     

   

   

     

     

     

   

   

     

     

     

   

   

     

     

     

   

 

First:

       

       

 runat="server" TargetControlID="txtFirst"

         CalculatorControlID="extTotal" />

     

Second:

       

       

 runat="server" TargetControlID="txtSecond"

         CalculatorControlID="extTotal" />

     

Third:

       

       

 runat="server" TargetControlID="txtThird"

         CalculatorControlID="extTotal" />

     

Fourth:

       

       

 runat="server" TargetControlID="txtFourth"

         CalculatorControlID="extTotal" />

     

Fifth:

       

       

 runat="server" TargetControlID="txtFifth"

         CalculatorControlID="extTotal" />

     

Sixth:

       

       

 runat="server" TargetControlID="txtSixth"

         CalculatorControlID="extTotal" />

     

Summation:

       

       

 TargetControlID="lblTotal" OnClientControlValueUnassigned=

 "CalculatorExtender_ControlValueUnassigned" />

     

Figure 13: Testing the extenders.

 

As the user enters numbers into the form, the extender populates at the bottom, as shown in Figures 14-16. The user begins to enter the next number, which continues the summation.

 


Figure 14: Entering the initial number.

 


Figure 15: Continuing to enter numbers.

 


Figure 16: Entering more numbers.

 

Conclusion

We ve examined a practical example of developing an AJAX component using the AJAX Control Toolkit approach to developing server extenders. Through the use of two extenders, mapped to each other, it s possible to create reusable code that simply can be dragged and dropped throughout the page. This solution dramatically reduces the amount of JavaScript needed to perform this function.

 

We took an extensive look at the AJAX Control Toolkit approach to developing extenders. This approach differs from the ASP.NET AJAX approach, at least from the server perspective. We ve also looked at the new conventions that ASP.NET AJAX employs on the client side, and how the server components map to the client components to develop reusable solutions.

 

C# and VB.NET source code accompanying this article is available for download.

 

Brian Mains is a consultant with Computer Aid Inc., where he works with non-profit and state government organizations. He was awarded the Microsoft Most Valuable Professional award in July 2007 and 2008 and has been active on several .NET forums and Web sites. You can catch him on his blog at http://dotnetslackers.com/Community/blogs/bmains/default.aspx.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish