Table of Contents
In this article, I'll cover the OOP principles in the MS AJAX JavaScript library. It is quite a big topic, but I decided to highlight only the essential things that should be known by developers for creating server side controls extended by client capabilities.
This article could also be useful as a quick help if you don't remember how exactly to work with some OOP extensions in the MS AJAX JS Library.
You will understand how the ASP.NET AJAX controls work. You will understand their client side code, and thus able to debug and discover problems faster.
I assume the reader knows the basics of ASP.NET development, and also the basic terms related with Object Orientated Programming (OOP). It is good to know what the terms class, object, interface, enumerator, and inheritance mean.
Each code listed in this article has a description below itself. The description contains the path to the file in the download package. Each code is included in the example page, so you can simply run it from Visual Studio and see how it works.
The MS AJAX JavaScript Library is only a bunch of files with a js extension. These files are precompiled as embedded resources in the system.web.extensions.dll assembly. By putting the ScriptManager
control into your page, you ensure that the library will be loaded.
Note: The ScriptManager
control takes care of loading JavaScript resources into your web page. It is available from ASP.NET 3.5. If you decide to use the ScriptManager
, you should register all the JavaScript resources via this control. By using this, you can avoid many problems caused by wrong loading of JavaScript. There is much to tell you about the ScriptManger
but it's not the topic of this article, therefore if you are interested in it, take a look at reference [2].
Let's start with OOP extensions. Basically, the big advantage (from my point of view) is that MS AJAX JavaScript Library allows you to think JavaScript as an OOP language. In the sections marked with "OOP extensions ...", I'll introduce some of the techniques to work with classes, inheritance, and enumerators.
In this example, I will show you how to create a class, how to make an instance of it, and how to call a method on that instance.
Type.registerNamespace("DotNetSources.Customer");
DotNetSources.Customer = function() {
this._name = null;
this._dateOfBirth = null;
}
DotNetSources.Customer.prototype = {
toString: function() {
return "Name: " + this.get_name() +
", Date of birth: " + this.get_dateOfBirth();
},
displayInfo: function() {
alert(this.toString());
},
get_name: function() {
return this._name;
},
set_name: function(value) {
this._name = value;
},
get_dateOfBirth: function() {
return this._dateOfBirth;
},
set_dateOfBirth: function(value) {
this._dateOfBirth = value;
}
}
DotNetSources.Customer.registerClass('DotNetSources.Customer');
Listing 1 - The class definition (01_creating_class/Customer.js)
As you can see, it's a simple class named Customer
in the namespace DotNetSources
. The class contains two variables (two properties): name
and dateOfBirth
. Let's look at the code to call it:
<div>
Name: <input type="text" id="txtName" /><br />
Date of Birth: <input type="text" id="txtDateOfBirth" /><br />
<input type="button"
value="Create class and call DisplayInfo method."
onclick="btnClick()" />
</div>
<script language="javascript">
function btnClick() {
var myCustomer = new DotNetSources.Customer();
myCustomer.set_name($get('txtName').value);
myCustomer.set_dateOfBirth($get('txtDateOfBirth').value);
myCustomer.displayInfo();
}
</script>
Listing 2 - How to work with the JS instance (01_creating_class/Default.aspx)
I hope there is nothing complicated in Listing 2. I will just mention the function $get
. This function comes from the MS AJAX JS library, and it behaves in the same way as document.getElementById()
. The screen-shot of the precedent example is on Figure 2.
Figure 2 - Creating the class screenshot
Although I like OOP extensions, I have to say that I have never used interfaces in JavaScript. But it's good to introduce an example. Creating an interface is similar to creating a class. The only two differences are:
- You are not doing the implementation of the interface, just putting
throw Error.notImplemented();
in the implementation of the methods.
- You call
registerInterface
instead of registerClass
at the end of the script.
Let's look at the example in Listing 3.
Type.registerNamespace("DotNetSources.IComparable");
DotNetSources.IComparable = function() {
throw Error.notImplemented();
}
DotNetSources.IComparable.prototype = {
compare: function(value) {
throw Error.notImplemented();
}
}
DotNetSources.IComparable.registerInterface('DotNetSources.IComparable');
Listing 3 - Creating an interface (02_Interfaces/IComparable.js)
Implementing an interface is also very simple. Just create a class, and in the registerClass
function, specify the interface. It is the third parameter of the function. The second parameter is the base class (I'll talk about inheritance in the next chapter). The class can implement many interfaces, thus you can write a list of interfaces separated by comma. Look at the following example for better understanding:
Type.registerNamespace("DotNetSources.Number");
DotNetSources.Number = function() {
this._a = null;
}
DotNetSources.Number.prototype = {
compare: function(value) {
return this._a > value._a;
},
get_a: function() {
return this._a;
},
set_a: function(value) {
this._a = value;
}
}
DotNetSources.Number.registerClass('DotNetSources.Number', null,
DotNetSources.IComparable);
Listing 4 - Implementing interface (02_Interface/Number.js)
Inheritance is much more interesting than interfaces. It plays an important role in creating AJAX controls. Because, in the case of AJAX controls, we will need to derive from JavaScript classes created in the MS AJAX JavaScript Library. But now, we will focus on the simple inheritance of two objects created by us. Look at the following UML class diagram for a visual presentation:
Figure 3 - Class diagram of JavaScript classes
As you have probably noted, in this example, I'll cover how to override a method and how to call its base
method as well. Let's discover how it looks in JavaScript code.
The Person
class:
Type.registerNamespace("DotNetSources.Person");
DotNetSources.Person = function() {
this._name = null;
}
DotNetSources.Person.prototype = {
toString: function() {
return "Name: " + this.get_name();
},
displayInfo: function() {
alert(this.toString());
},
get_name: function() {
return this._name;
},
set_name: function(value) {
this._name = value;
}
}
DotNetSources.Person.registerClass('DotNetSources.Person');
Listing 5 - The Person class (03_Inheritance/Person.js)
We've already created a class in JavaScript, thus there aren't any surprises in Listing 5, and so, we can go on with the Customer
class:
Type.registerNamespace("DotNetSources.Customer");
DotNetSources.Customer = function() {
DotNetSources.Customer.initializeBase(this);
this._lastVisit = null;
}
DotNetSources.Customer.prototype = {
toString: function() {
var b = DotNetSources.Customer.callBaseMethod(this, 'toString');
return b + ", Last Visit: " + this.get_lastVisit();
},
get_lastVisit: function() {
return this._lastVisit;
},
set_lastVisit: function(value) {
this._lastVisit = value;
}
}
DotNetSources.Customer.registerClass('DotNetSources.Customer', DotNetSources.Person);
Listing 6 - The Customer class (03_Inheritance/Customer.js)
The Customer
class, on the other hand, contains new artifacts for us. Let's see these artifacts in detail:
- In the constructor, call
DotNetSources.Customer.initializeBase(this);
in order to initialize the methods and properties of the base
class. In our case, it is Person
.
- In the call
registerClass
at the end of the script, specify the derived class as the second parameter, like this: DotNetSources.Customer.registerClass('DotNetSources.Customer', DotNetSources.Person);
.
- When you override a method and want to call its
base
method, call callBaseMethod
and specify the current instance and the name of the method as a string
. Like the following: DotNetSources.Customer.callBaseMethod(this, 'toString');
.
The Friend
class:
Type.registerNamespace("DotNetSources.Friend");
DotNetSources.Friend = function() {
DotNetSources.Friend.initializeBase(this);
this._dateOfBirth = null;
}
DotNetSources.Friend.prototype = {
toString: function() {
var b = DotNetSources.Friend.callBaseMethod(this, 'toString');
return b + ", Date of Birth:: " + this.get_dateOfBirth();
},
get_dateOfBirth: function() {
return this._dateOfBirth;
},
set_dateOfBirth: function(value) {
this._dateOfBirth = value;
}
}
DotNetSources.Friend.registerClass('DotNetSources.Friend', DotNetSources.Person);
Listing 7 - The Friend class (03_Inheritance/Friend.js)
The classes introduced in Figure 3 have been created, and now it's time to use it. This is done by the script in Listing 8.
<asp:ScriptManager runat="server" ID="sManager">
<Scripts>
<asp:ScriptReference Path="~/03_Inheritance/Person.js" />
<asp:ScriptReference Path="~/03_Inheritance/Customer.js" />
<asp:ScriptReference Path="~/03_Inheritance/Friend.js" />
</Scripts>
</asp:ScriptManager>
<div>
Name: <input type="text" id="txtName" /><br />
Date of Birth or Last Visit: <input type="text" id="txtDateOfBirth" /><br />
<input type="button"
value="Create an instance of Customer and call
DisplayInfo method which is defined in Person class."
onclick="btnCustomer()" />
<br />
<input type="button"
value="Create an instance of Friend and call DisplayInfo
method which is defined in Person class."
onclick="btnFriend()" />
</div>
</form>
<script language="javascript" type="text/javascript">
function btnCustomer() {
var myCustomer = new DotNetSources.Customer();
myCustomer.set_name($get('txtName').value);
myCustomer.set_lastVisit($get('txtDateOfBirth').value);
myCustomer.displayInfo();
}
function btnFriend() {
var myFriend = new DotNetSources.Friend();
myFriend.set_name($get('txtName').value);
myFriend.set_dateOfBirth($get('txtDateOfBirth').value);
myFriend.displayInfo();
}
</script>
Listing 8 - Using classes from Figure 3 (03_Inheritance/Default.aspx)
I have got some screenshots for you. In Figure 4 is the result of the function btnCustomer()
from Listing 8, and in Figure 5 is the result of the function btnFriend()
from Listing 8.
Figure 4 - Creating a Customer instance and calling displayInfo on it.
Figure 5 - Creating Friend instance and calling displayInfo on it.
As you can see on the screenshots, both Figure 4 and Figure 5 provide different results although I'm calling the same method named displayInfo
which is defined only on the Person
class. The concrete implementations of the Person
class (Customer
and Friend
) override the toString
method and providing different strings.
Although it is unbelievable, there are enumerators as well. I assume everyone knows what an enumerator is, so we can go directly to the example.
Type.registerNamespace('DotNetSources');
DotNetSources.Severity = function() {
}
DotNetSources.Severity.prototype = {
Error: 1,
Warning: 2,
Info: 3
}
DotNetSources.Severity.registerEnum('DotNetSources.Severity');
Listing 9 - Severity enumerator (04_Enum/Severity.js)
Furthermore, an enumerator represents a number. For example, calling:
DotNetSources.Severity.Error
gives you the number 1. See the following screenshot based on the example from the download package (04_Enum/Default.aspx).
Figure 6 - The screenshot of getting the value of the enumerator. The first button was pressed.
Every example introduced in this article use the objects when clicking on a button. This is a quite safe state because everything is loaded and initialized. However, there could be a situation where you need to use the objects when the web page is requested. Obviously, you can't just put script
tags everywhere and write some code working with the objects because you are not sure if the ScriptManager
already did the initialization of the MS AJAX JS Library or not. In this situation, it is good to know something about the ASP.NET AJAX client life-cycle events. All the stages can be discovered on reference [3]. In this article, I show you how to work with pageLoad
.
Let's stop talking, and see an example. I use the class Customer
from Listing 1. When the page is requested, I create an instance of the Customer
class and I call a method on it. See the following piece of code:
<asp:ScriptManager runat="server" ID="sManager">
<Scripts>
<asp:ScriptReference Path="~/01_creating_class/Customer.js" />
</Scripts>
</asp:ScriptManager>
<div>
</div>
<script type="text/javascript">
function pageLoad() {
var myCustomer = new DotNetSources.Customer();
myCustomer.set_name('Petr');
myCustomer.set_dateOfBirth('02.02.1983');
myCustomer.displayInfo();
}
</script>
Listing 11 - Working with objects when a page is loaded (06_PageLoad/Default.aspx)
As you can see in Listing 11, I have put my code into the pageLoad
function. The pageLoad
function is called automatically after the MS AJAX JS Library is initialized. Thus, you are sure you can make the instances of your classes.
You probably know that via JavaScript you can register handlers for DOM elements. Consider a situation where you have a button in a page. Using JavaScript, you want to tell that when the user clicks on the button, an XX JavaScript function will be performed. Maybe you know how to do that in raw JavaScript, but in raw JavaScript, the implementation is different for each browser. MS AJAX JS Library is oriented towards you, and provides only one approach to add these handlers to DOM elements. The magic function is named $addHandler/$removeHandler
, and the definition is:
$addHandler(element,"eventName", handler);
$removeHandler(element,"eventName", handler);
element
- It is the DOM element. You can reach it using document.getElementById
from raw JavaScript, or shortly using $get
, which is the same as document.getElementById
. I recommend you to use $get
because $get
is on the highest level of an abstraction.
eventName
- is the string. It is the name of the event of the element from the first parameter. You can see the list of events for each element on reference [4]. Just click on the tag you are interested in and see the table "Event Attributes". The eventName
here is without the "on" prefix. Thus, instead of onclick
, you pass only click
.
handler
- is the name of the function in JavaScript. Basically, if you are working with simple functions in JavaScript and you are not using classes, you can pass here the name of the function. However, when you are working with classes and want to pass one of the methods of the class, then you have to create the handler using the Function.createDelegate
function. It is good to keep the result of Function.createDelegate
as one of the local variables of the class in order to be able to call $removeHandler
on dispose or page unload.
Let's consider the following example:
Create a JavaScript class which takes two arguments: the ID of the text box and the ID of the DIV (I'm talking about the IDs of the elements in the DOM). The class itself will take care of the following functionality: When the user types a letter into the text box, the content of the text box will appear in the DIV, letter after letter. The HTML for such a page can look like:
<div>
Type something here: <input type="text" id="txtInput" /><br /><br />
You are typing: <div id="lblEcho"></div>
</div>
<script type="text/javascript">
function pageLoad() {
var status = new DotNetSources.StatusInfo('txtInput', 'lblEcho');
status.sniff();
}
</script>
Listing 10 - (05_Events/Default.aspx)
As you can see in Listing 10, there is a text box and a DIV. The JavaScript code has two lines:
var status = new DotNetSources.StatusInfo('txtInput', 'lblEcho');
status.sniff();
Here, you have the text box ID, the DIV ID, and can sniff what I'm writing into the text box. Now, it's time to look at the implementation of the DotNetSources.StatusInfo
class.
Type.registerNamespace("DotNetSources.StatusInfo");
DotNetSources.StatusInfo = function(textBoxId, labelId) {
this._textBoxId = textBoxId;
this._labelId = labelId;
this._onkeyup = null;
this._onDispose = null;
}
DotNetSources.StatusInfo.prototype = {
sniff: function() {
var textBox = $get(this._textBoxId);
this._onkeyup = Function.createDelegate(this, this._keyup);
this._onDispose = Function.createDelegate(this, this._dispose);
$addHandler(textBox, 'keyup', this._onkeyup);
Sys.Application.add_unload(this._onDispose);
},
_keyup: function() {
$get(this._labelId).innerText = $get(this._textBoxId).value;
},
_dispose: function() {
$removeHandler($get(this._textBoxId), 'keyup', this._onkeyup);
Sys.Application.remove_unload(this._onDispose);
}
}
DotNetSources.StatusInfo.registerClass('DotNetSources.StatusInfo');
Listing 11 - DotNetSources.StatusInfo class (05_Events/StatusInfo.js)
We went through the principles to work with classes created in the MS AJAX JS Library. In the following chapters, I'll write the basics about creating ASP.NET AJAX controls. If you read the precedent chapters of this article, it will be very easy for you to understand how the ASP.NET AJAX controls work.
In this section, I will assume that you know how to create a Web Control in ASP.NET.
An ASP.NET AJAX control is nothing other than a simple Web Control which has a mirroring JavaScript class on the client side. When you are creating an ASP.NET AJAX control, you have to create a JavaScript class which derives from Sys.UI.Control
and associate this JavaScript class with the control written in .NET (using C#, VB, etc.). Look at the following visual presentation:
Figure 7 - WebControl with JavaScript class
I have also prepared the example for you. Consider the DIV element as a square with a red background. When you click on the DIV, the alert box with the message will be displayed. How do we reach this functionality using the ASP.NET AJAX control is shown in Listing 12 and Listing 13.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
[assembly: WebResource("DotNetSources_e0003._07_Web_Control.RedSquare.js",
"text/javascript", PerformSubstitution = true)]
namespace DotNetSources_e0003
{
public class RedSquare : WebControl, IScriptControl
{
public RedSquare()
: base(HtmlTextWriterTag.Div)
{
}
ScriptManager sm;
protected override void OnPreRender(EventArgs e)
{
this.Style.Add(HtmlTextWriterStyle.BackgroundColor, "Red");
this.Width = this.Height = 200;
if (!this.DesignMode)
{
sm = ScriptManager.GetCurrent(Page);
sm.RegisterScriptControl(this);
}
base.OnPreRender(e);
}
protected override void Render(HtmlTextWriter writer)
{
if (!this.DesignMode)
{
sm.RegisterScriptDescriptors(this);
}
base.Render(writer);
}
public IEnumerable<System.Web.UI.ScriptDescriptor> GetScriptDescriptors()
{
ScriptControlDescriptor descriptor =
new ScriptControlDescriptor("DotNetSources.RedSquare", this.ClientID);
return new ScriptDescriptor[] { descriptor };
}
public IEnumerable<System.Web.UI.ScriptReference> GetScriptReferences()
{
ScriptReference jsReference = new ScriptReference();
jsReference.Assembly = "DotNetSources_e0003";
jsReference.Name = "DotNetSources_e0003._07_Web_Control.RedSquare.js";
return new ScriptReference[] { jsReference };
}
}
}
Listing 12 - The RedSquare class; C# class (07_Web_Control/RedSquare.cs)
Type.registerNamespace("DotNetSources.RedSquare");
DotNetSources.RedSquare = function(element) {
DotNetSources.RedSquare.initializeBase(this, [element]);
this._onClickHandler = null;
}
DotNetSources.RedSquare.prototype = {
initialize: function() {
DotNetSources.RedSquare.callBaseMethod(this, 'initialize');
this._onClickHandler = Function.createDelegate(this, this.onClick);
$addHandler(this.get_element(), "click", this._onClickHandler);
},
onClick: function() {
alert('You clicked on the red square.');
},
dispose: function() {
$clearHandlers(this.get_element());
DotNetSources.RedSquare.callBaseMethod(this, 'dispose');
}
}
DotNetSources.RedSquare.registerClass('DotNetSources.RedSquare', Sys.UI.Control);
Listing 13 - The RedSquare class, JavaScript class (07_Web_Control/RedSquare.js)
I didn't mention how precisely the JavaScript class and the WebControl
class are connected, but I guess you noted the IScriptControl
interface. There are two methods on the IScriptControl
interface used to connect the control with the JavaScript resource. If you want to know more about IScriptControl
, you can follow reference [5].
Many people like to build User Controls (template controls) instead of Web Controls. The JavaScript class in this case is a little bit different. Basically, the User Control has no root element; therefore, you can't derive the JavaScript class from Sys.UI.Control
. However you can derive it from Sys.Component
. If you want to see a visual representation, you can look at Figure 7 and imagine Sys.Component
instead of Sys.UI.Control
and System.UI.UserControl
instead of System.UI.Web.WebControls.WebControl
.
Also, in this chapter, I have prepared an example for you. In this example, I added the handover values between the server control and the JavaScript class. Please notice the GetScriptDescriptors
method in Listing 14 to see how to pass values from the server control to the JavaScript class.
[assembly: WebResource("DotNetSources_e0003._08_User_Control.TableOfFourColors.js",
"text/javascript", PerformSubstitution = true)]
namespace DotNetSources_e0003._08_User_Control
{
public partial class TableOfFourColors : UserControl, IScriptControl
{
protected void Page_Load(object sender, EventArgs e)
{
}
ScriptManager sm;
protected override void OnPreRender(EventArgs e)
{
if (!this.DesignMode)
{
sm = ScriptManager.GetCurrent(Page);
sm.RegisterScriptControl(this);
}
base.OnPreRender(e);
}
protected override void Render(HtmlTextWriter writer)
{
if (!this.DesignMode)
{
sm.RegisterScriptDescriptors(this);
}
base.Render(writer);
}
public IEnumerable<System.Web.UI.ScriptDescriptor>
GetScriptDescriptors()
{
ScriptControlDescriptor descriptor =
new ScriptControlDescriptor(
"DotNetSources.TableOfFourColors", this.ClientID);
descriptor.AddProperty("pnlRed", pnlRed.ClientID);
descriptor.AddProperty("pnlBlue", pnlBlue.ClientID);
descriptor.AddProperty("pnlYellow", pnlYellow.ClientID);
descriptor.AddProperty("pnlGreen", pnlGreen.ClientID);
return new ScriptDescriptor[] { descriptor };
}
public IEnumerable<System.Web.UI.ScriptReference>
GetScriptReferences()
{
ScriptReference jsReference = new ScriptReference();
jsReference.Assembly = "DotNetSources_e0003";
jsReference.Name = "DotNetSources_e0003." +
"_08_User_Control.TableOfFourColors.js";
return new ScriptReference[] { jsReference };
}
}
}
Listing 14 - The TableofFourColors class, C# class - (08_User_Control/TableOfFourColors.ascx.cs)
Type.registerNamespace("DotNetSources.TableOfFourColors");
DotNetSources.TableOfFourColors = function() {
DotNetSources.TableOfFourColors.initializeBase(this);
this._onClickHandlerRed = null;
this._onClickHandlerBlue = null;
this._onClickHandlerYellow = null;
this._onClickHandlerGreen = null;
this._pnlRed = null;
this._pnlBlue = null;
this._pnlYellow = null;
this._pnlGreen = null;
}
DotNetSources.TableOfFourColors.prototype = {
initialize: function() {
DotNetSources.TableOfFourColors.callBaseMethod(this, 'initialize');
this._onClickHandlerRed =
Function.createDelegate(this, this.onClickRed);
this._onClickHandlerBlue =
Function.createDelegate(this, this.onClickBlue);
this._onClickHandlerYellow =
Function.createDelegate(this, this.onClickYellow);
this._onClickHandlerGreen =
Function.createDelegate(this, this.onClickGreen);
$addHandler($get(this.get_pnlRed()),
"click", this._onClickHandlerRed);
$addHandler($get(this.get_pnlBlue()),
"click", this._onClickHandlerBlue);
$addHandler($get(this.get_pnlYellow()),
"click", this._onClickHandlerYellow);
$addHandler($get(this.get_pnlGreen()),
"click", this._onClickHandlerGreen);
},
onClickRed: function() {
alert('You clicked on the red part.');
},
onClickBlue: function() {
alert('You clicked on the blue part.');
},
onClickYellow: function() {
alert('You clicked on the yellow part.');
},
onClickGreen: function() {
alert('You clicked on the green part.');
},
get_pnlRed: function() {
return this._pnlRed;
},
set_pnlRed: function(value) {
this._pnlRed = value;
},
get_pnlBlue: function() {
return this._pnlBlue;
},
set_pnlBlue: function(value) {
this._pnlBlue = value;
},
get_pnlYellow: function() {
return this._pnlYellow;
},
set_pnlYellow: function(value) {
this._pnlYellow = value;
},
get_pnlGreen: function() {
return this._pnlGreen;
},
set_pnlGreen: function(value) {
this._pnlGreen = value;
},
dispose: function() {
$clearHandlers($get(this.get_pnlRed()));
$clearHandlers($get(this.get_pnlBlue()));
$clearHandlers($get(this.get_pnlYellow()));
$clearHandlers($get(this.get_pnlGreen()));
DotNetSources.TableOfFourColors.callBaseMethod(this, 'dispose');
}
}
DotNetSources.TableOfFourColors.registerClass(
'DotNetSources.TableOfFourColors', Sys.Component);
Listing 15 - The TableofFourColors class, JavaScript class - (08_User_Control/TableOfFourColors.js)
Thank you for reading. I hope it was useful for you. As usual, I'm interested in feedback, so don't hesitate to write a comment or vote. Thank you very much.
- 29 January 2010 - Initial release.