Reuse, Recycle, Extend
Create an AJAX Extender Using the AJAX Control Toolkit
October 30, 2009
CoverStory
LANGUAGES:C# | VB.NET
ASP.NETVERSIONS: 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 forJavaScript libraries on the Internet. The idea behind a JavaScript library isto make JavaScript development easier. After all, developing applicationssolely through AJAX requires 100 times more code than a conventional ASP.NETapplication.
ASP.NET AJAX is a JavaScript library that uses what s beendone in the .NET Framework, and structures itself enough to mimic the .NET Framework.Because the JavaScript framework doesn t support some of the conventions thatthe .NET Framework uses, there are some workarounds required to put theseconventions in place. Although it is expected you are familiar with theseconventions, I ll try to touch on them as I go.
The example in this article creates an AJAX extender. Anextender control does not render its own user interface; by that I mean thatextender controls emit no user interface at rendering time. An extender targetsanother control through its TargetControlID property; the targeted control isthe recipient of the client JavaScript code emitted by the extender component. Thisis the process of extending existing .NET controls.
Whether the object is an AJAX control or extender, thereare two pieces that are consistently present: a server component and a clientcomponent. A server component, which is a control that resides in a classlibrary, defines the control portion of the extender. This server component canbe shown in the Visual Studio toolbox and dragged to an ASP.NET page. Theserver component works in tandem with a client component. This client componentexists 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 withextenders. There are some options when it comes to developing custom AJAXextenders. Initially, the ASP.NET AJAX Framework created the System.Web.UI.ExtenderControlserver component. The extender control requires the developer to implement theGetScriptDescriptors and GetScriptReferences methods. These two methods areresponsible for describing the properties, methods, and events of the clientcomponent and specifying where the JavaScript file resides, respectively.
This extender control has a client-counterpart namedSys.UI.Behavior, which has all of the client-side features that are equallyexposed on the server side. This component inherits from Sys.Component andexposes the base set of properties, methods, and events needed for working withextenders.
When the AJAX Control Toolkit was released, another optionwas present: AjaxControlToolkit.ScriptControlBase. This server component uses acompletely different approach to developing AJAX components. Overall, I thinkit s much better than its predecessor; however, it takes some getting used to. Thisapproach uses attributes to specify the location of the script and thedescription of the component. Previously, with the ExtenderControl base class,the developer implemented the description/registration in theGetScriptDescriptors and GetScriptReferences methods; using the AJAX Control Toolkitapproach, these methods are rarely used or needed.
Let s take a look at the options the AJAX Control Toolkit provides:
AjaxControlToolkit.ExtenderControlBase. Thetoolkit base class for server extender components. ExtenderControlBase inheritsfrom the System.Web.UI.ExtenderControl class and extends on what properties ormethods are available.
AjaxControlToolkit.BehaviorBase. The base classfor client extender components. Every server component has a client componentcounterpart that builds on the previous architecture.
Before we get into the example code, let s take a look atthe 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, thishappens through the use of attributes. At run time, the ExtenderControlBasebase class reflects on the properties and methods of the component and examinestheir attributes for specific attribute types. If the attributes are AJAXControl Toolkit attributes, the script dynamically creates the descriptor basedon 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 theproperty above maps to a property in the client component. The value of theproperty will be assigned to the client component to be used as its initial(and possibly only) value.
ClientPropertyName. This contains the name ofthe property on the client component. If omitted, the name of the property isused in the exact case instead. Normally, JavaScript uses lower-camel case,where C# and VB.NET classes use upper-camel case, and thereforeClientPropertyName can be used to handle that difference.
IDReferenceProperty. This specifies that thefield contains the ID of another control; at run time, this ID will betranslated into the ClientID of the underlying HTML element. Finding the IDvalue can be difficult sometimes, especially with the use of master pages. Eventhough every control on a page may have a unique ID, at run time, the .NET Frameworkgives each control a unique ID stored in both the ClientID and UniqueIDproperties. There are a couple of options when using IDs, which I ll discusslater.
Any attribute can be specified; however, only certainattributes matter to the AJAX Control Toolkit process. Let s look at an exampleof an event definition:
[
ClientPropertyName("totalUpdated"),
ExtenderControlEvent
]
public string OnClientTotalUpdated { .. }
Notice an event definition is defined as a property. Anevent definition uses a property value to read/write the name of an eventhandler (essentially the JavaScript method name). The ExtenderControlEventattribute signals at run time that the property is supplying an event handlerdefinition. The ClientPropertyName attribute specifies that the event s clientname is the totalUpdated event. If OnClientTotalUpdated has the name of theJavaScript method Component_TotalUpdated, this method name is supplied as anevent handler to the totalUpdated event. We ll see how this works later. Let snow look at how the script gets registered, which occurs at the class level (seeFigure 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 classCalculatedFieldExtender
:ExtenderControlBase
{
}
}
Figure 1: Script registrationusing an embedded resource.
I mentioned earlier that scripts get registered throughthe GetScriptReferences method. In the ASP.NET AJAX Framework, the scriptpoints to the location of the JavaScript file containing the client componentcode. ASP.NET AJAX deploys scripts by storing them as an embedded resource andretrieving them through the Page.ClientScript.GetWebResourceUrl method. TheAJAX Control Toolkit automates this by specifying the name of the component andfile in the ClientScriptResource attribute. The name of the JavaScript file isnot the actual file name, but the resource name that the .NET Framework givesit (the fully quantified resource name).
Component Description
As I scour the Web and look at forum posts, I occasionallysee questions about the ability to perform summation of controls using an AJAXapproach. This can be seen as a calculator-type effect, where the first x number of textboxes in a row or columnare the input values, and the final textbox or label is the summed result.
In typical JavaScript, the approach to handle this is toattach an onkeypress or onkeyup handler to the textboxes. When the textboxfires its keyup event, all the textboxes on the page are summed and the finalsummation is displayed in a textbox or label. This occurs as the user changesthe value by a single key press, displaying an immediate calculated sum to theuser. Although initially the summed value is incorrect because most of thevalues have not yet been provided, as the user enters more numbers into thetextboxes, the total amount becomes apparent.
I wanted to create reusable logic though, and not stick tothe ordinary approach. Even though achievable through ordinary JavaScript, theexample I created for this article uses extenders that target textbox controlsas the source and detects any changes. When the value changes as numbers areentered, the extender passes this value to another extender, whoseresponsibility is to sum the values.
This means that the CalculatedFieldExtender, which we llsee soon, targets a textbox control, as well as maintains a reference to theCalculatorExtender that sums the values. When its value changes through a keypress, this change in values notifies the CalculatorExtender, and this extenderreceives the update request through an event.
For the CalculatorExtender to know what the final valueis, it gets a reference to all the CalculatedFieldExtender extenders that pointto it (because there can be more than one CalculatorExtender instance). Once ithas a reference to these extenders, it needs to loop through the extenders, getthe current value, and calculate the output value.
Calculating Individual Fields
Let s start by looking at the definition of theCalculatedFieldExtender, whose responsibility is to map to the textbox and passalong its value. The extender inherits from ExtenderControlBase and definessome attributes about the script. Let s look at the shell of the servercomponent (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 classCalculatedFieldExtender
:ExtenderControlBase
{
}
}
Figure 2: The CalculatedFieldExtenderserver component shell.
I explained the process of registering scripts for Figure1, so I won t explain it again. The extender exposes two properties (shown in Figure3): one stores the summation control s ID; the second stores the name of aJavaScript 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 servercomponent s properties.
Notice the two properties only differ slightly, butrepresent two completely different purposes. Both use lower-camel case as theirclient name, and retain their values in ViewState. The event definition, byconvention, prefixes the event name with OnClient . The only major differenceis that one property defines the ExtenderControlProperty attribute, and onedefines the ExtenderControlEvent attribute.
In our example, these property values are pushed down tothe client. The value supplied to the CalculatorControlID is supplied to thecalculatorControlID client property (or the set_calculatorControlID propertysetter). The OnClientFieldValueChanged property value is pushed down to theadd_fieldStatusChanged method, which registers the defined method for thatevent (if the method isn t null). We ll get to this in a bit; however, I wantto move next into the structure of the client component. Figure 4 contains theshell 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 clientcomponent shell.
There are four main sections to this script:
Constructor definition. When working with AJAXcontrols or extenders, a one-parameter constructor is required. This parameteris the HTML element being rendered or extended. The extender or control handlesaccessing this object for you automatically. For the base class to workcorrectly, the initializeBase method passes the associatedElement in array formup to the base class.
Prototype definition. The properties, methods,events, etc. for the client component are defined in the prototype, as we llsee soon.
Descriptor definition. Describes the component,listing the properties, events, methods, etc. that the component exposes.
Script registration. Class registration isrequired so the ASP.NET AJAX Framework knows of the class existence, inaddition to the base class and interfaces it implements. Script notification isalso required to ensure that the application knows that it has loaded.
I m going to discuss the first two, which are the mostrelevant to the topic at hand.
Constructor Definition
Figure 5 shows the definition of the constructor. Theconstructor performs two functions. First, it passes the associated HTMLelement to the base class through initializeBase. This HTML element is lateravailable using the element property (accessible through the getter,get_element). Second, it defines all the variables used in the script. Variablesshould 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 clientcomponent constructor.
Prototype Definition
The prototype is the body of the class. Whenever aninstance is created of that class type, the prototype defines the members ofthe instance. The syntax changes a little bit; instead of defining the functionkeyword at the beginning, it shifts it to the end (as shown in Figure 6).
Nucleo.Web.MathControls.CalculatedFieldExtender.prototype =
{
get_calculatorControlID : function()
{
returnthis._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: Propertiesand events.
Previously I mentioned that the server component pushesvalues up to the client. The CalculatorControlID property pushed the client IDof the CalculatorExtender to the set_calculatorControlID property setter, andthe OnClientFieldStatusChanged property pushes the JavaScript method name tothe add_fieldValueChanged method. These members are shown in Figure 6.
The code in Figure 6 is pretty straightforward; propertieshave getters and setters noted by the get_ and set_ prefixes. Events havemethods to add and remove handlers using the add_ and remove_ prefix. Eventsalso can be raised using the raise_ or _on prefix. Property setters onlychange the underlying variable value when there is a difference in values; theproperty changed event is raised when this happens.
The prototype contains two other important lifecyclemethods: initialize and dispose. As you can imagine, initialize fires at thebeginning of the lifecycle process (before the client init event) and disposefires at the end. It is in the initialize method that the text control s keypress event is mapped to a client method, which checks for key presses and passesthis notification to the calculator.
In VB.NET, the structure to add an event handler for anevent is a two-step process. First, a method with a matching event signature isdefined in the code. The second part of the process is to map this method as anevent handler for an event of a class through the AddHandler method.
On the client side, the process is similar (again, througha two-step process). First, a new add-on to the Function object creates adelegate to the event handler through the createDelegate method. This methodreturns a handler that points to the event handler. The second step is to usethe $addHandler static method, which maps the HTML element s event to thisdelegate. This two step process is shown in Figure 7. Note that the initializeand 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: Initializingthe event handlers.
The call to callBaseMethod calls the initialize method onthe base class; you may wonder what the difference is between initializeBaseand callBaseMethod. The initializeBase method is used for the constructor only;everywhere else, callBaseMethod should be used (which invokes a method call onthe base class definition).
In the initialize method, the keyPressCallback andkeyUpCallback methods fire when a key is pressed in a targeted control (seeFigure 8). The value in the text control is evaluated to ensure it really is anumber using another new function: parseInvariant. If really a number, thatvalue 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 thefieldValueChanged event fires; whenever the value changes, any subscriber tothe event (who called the add_fieldValueChanged method) gets a notification ofthat event. This notification is important because the CalculatorExtender, whichsums up the values, needs to know when an extender s targeted control has itsvalue changed. The CalculatorExtender uses this event to recalculate its totaland supply the new value to the control.
Let s think about this a little more. Suppose a form hassix input fields that take numeric values. As the user types numbers into thetextboxes, for every key press an event fires (fieldValueChanged). This eventis actually sending a notification to the second companion component, lettingthat component know that it needs to recalculate itself.
Suppose the user selects the first textbox, then pressesthe 2 key. The calculator extender now shows the value 2 . As the user keepstyping numbers, say 5 and 0 , the calculator shows 250 . The user tabs tothe next textbox, and enters 5 , 0 , 0 . The calculator now shows 750. Thisprocess continues for every textbox that has a CalculatedFieldExtender controlmapped to it. Now it s important to move on and understand how the calculationhappens.
The CalculatorExtender Component
The two components explained in this article are similarto each other. They both use an embedded script and define the type of controlit extends. However, one of the two differences is the TargetControlTypeattribute; to make the calculator flexible, any control can be extended to showthe 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 stringOnClientControlValueUnassigned
{
get { returnbase.GetPropertyValue(
"OnClientControlValueUnassigned", null); }
set {base.SetPropertyValue(
"OnClientControlValueUnassigned", value); }
}
[
ClientPropertyName("totalUpdated"),
ExtenderControlEvent
]
public stringOnClientTotalUpdated
{
get { returnbase.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 theGetPropertyValue and SetPropertyValue generic methods; these methods are helpermethods (defined in ExtenderControlBase) to work with ViewState. Noticeeverywhere else I use the null coalesce operator (??), but in this case I usedthese helper methods to read/write to ViewState.
As a bonus, the CalculatorExtender keeps a collection ofCalculatedFieldExtenders that map to this extender. Remember, theCalculatedFieldExtender has a reference to the CalculatorExtender with which itworks. This process works because the CalculatedFieldExtender calls theRegisterField method on the CalculatorExtender to register itself with thiscontrol. Figure 10 shows the code of the CalculatorExtender s RegisterFieldmethod 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);
CalculatorExtendercalculator = this.FindControlHelper(
this.CalculatorControlID)as CalculatorExtender;
if (calculator == null)
throw newArgumentException(string.Format(
"Thecalculator with an id of '{0}' could not be found.",
this.CalculatorControlID));
calculator.RegisterField(this);
}
Figure 10: RegisteringCalculatedFieldExtender controls.
On initialization, the CalculatedFieldExtender uses itsCalculatorControlID property to obtain a reference to the calculator. FindControlHelperis another useful method in the AJAX Control Toolkit s ExtenderControlBaseclass, which loops iteratively through the control collection looking for thedesired extender control (by its ID). So at initialization, each of the fieldextenders registers itself with the calculator, so the calculator knows with whichextenders it is working. Rather than storing this in ViewState, the collectionrecreates itself every page load at initialization time.
The same process also happens on the client side in prettymuch the same way. The _registerCalculatedField is called by theCalculatedFieldExtender client component so the CalculatorExtender knows whichextenders it needs to sum up using JavaScript code. Figure 11 shows thedefinition, 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: Registrationof extenders on the client side.
The list of extenders is stored in an array on the clientside. 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 alsohandy, but not required like it is on the client.
The list of extenders is exposed through a getter; thecalculator can simply loop iteratively through this list to calculate theircurrent values and post the summation. The calculator knows that the valuechanges through the fieldValueChangedCallback callback method that fires everytime a key is pressed in the underlying textbox. Notice in the_registerCalculatedField method that the calculator component registers to thefieldValueChanged event so it knows a recalculation is in order.
This recalculation process is the core of the work, and ithappens in both the calculate and _setControlTotal methods. These methods areresponsible for looping through the extenders, extracting the total values fromeach individual control, and summing its values, kicking out the total value tothe targeted control. The two places where this method is called is during theinitialization process and every time the fieldValueChanged event fires.
The process of displaying the final value may be easy ifthe CalculatorExtender had control over the user interface; however, to addflexibility, the extender tries to interrogate what the final controlrepresentation is (because, remember, the CalculatorExtender targets objects ofthe control type). Let s look at the definition, and I ll further explain thekey 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 { 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 valueto the value property if (element.value != null&& element.value != undefined) element.value =this._totalValue; //Otherwise, raise anevent to notify //that the value has beenunassigned else this.raise_controlValueUnassigned();}Figure 12: Refreshingthe summation. If the extender s collection is blank, the summationresult is zero; this is important, because if it simply exited withoutresetting the value, the previous total would still appear incorrectly (atclient time, extenders can be dynamically added or removed). When there areextenders registered on the client side, each of these extenders has acurrentValue property (defined as get_currentValue) that is interrogatediteratively. You may think it is unsafe to assume the current value is in itscorrect form; after all, the value could be text or some other type. However, I vetaken an extra step in the CalculatedFieldExtender to ensure that thecurrentValue property stores a legitimate numeric value. I ve removed the code that manipulates the UI to a privatemethod simply because this code is reused in multiple places. The_setControlTotal method s responsibility is to simply work with the userinterface; it checks to see if the HTML element being targeted has a valueproperty. For extra flexibility, an event is raised if the elementcan t be assigned a value (because it doesn t know what to assign the totalto). This forces the ASPX page to set the total. I could have checked theexistence of an innerText property, as well; however, I didn t want to do thatbecause it might not be the correct action (innerText may exist, but may be aninappropriate display for some controls). In the future, I may make thisconfigurable, but for now the controlValueUnassigned event signals to theconsumer that the total value could not be assigned to the underlying extendedcontrol, 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 alabel. Each textbox has a CalculatedFieldExtender mapped to it. The extendertaps in to the keyup event, checking for changes. Once the value changes, if itis a valid number, that number is passed to the CalculatorExtender, which loopsthrough each of the extenders on the client side and queries its currentValueproperty. If the current value of that textbox is not a numerical value, thevalue 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, butuses the innerText property on the client to make the assignment (because it isa span element). Figure 13 shows an example of the markup for the form. Asyou can see, it s not difficult to set up this example, and it s flexiblebecause TextBox/CalculatedFieldExtender controls can be added repeatedly tothis page without any client-side code changes. functionCalculatorExtender_ControlValueUnassigned( sender, e) { var extender = $find(''); var label = $get(''); label.innerText =extender.get_totalValue().toString(); } 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: Testingthe extenders. As the user enters numbers into the form, the extenderpopulates at the bottom, as shown in Figures 14-16. The user begins to enterthe next number, which continues the summation.
Figure 14: Entering the initialnumber.
Figure 15: Continuing to enternumbers.
Figure 16: Entering more numbers. Conclusion We ve examined a practical example of developing an AJAXcomponent using the AJAX Control Toolkit approach to developing serverextenders. Through the use of two extenders, mapped to each other, it spossible to create reusable code that simply can be dragged and droppedthroughout the page. This solution dramatically reduces the amount ofJavaScript needed to perform this function. We took an extensive look at the AJAX Control Toolkitapproach to developing extenders. This approach differs from the ASP.NET AJAXapproach, at least from the server perspective. We ve also looked at the newconventions that ASP.NET AJAX employs on the client side, and how the servercomponents map to the client components to develop reusable solutions. C# and VB.NET sourcecode accompanying this article is available for download. Brian Mains is aconsultant with Computer Aid Inc., where he works with non-profit and stategovernment organizations. He was awarded the Microsoft Most Valuable Professionalaward in July 2007 and 2008 and has been active on several .NET forums and Websites. You can catch him on his blog at http://dotnetslackers.com/Community/blogs/bmains/default.aspx.
About the Author
You May Also Like