Contents |
· 1 Introduction · 2 JavaScript Objects · 3 Classes and Namespaces · 4 Inheritance and Interfaces · 5 Properties · 6 Enumerations · 7 Events · 8 Static Methods · 9 Why the Different Constructs? · 10 Putting It All Together · 11 Conclusion |
The ASP.NET AJAX framework, along with the AJAX Control Toolkit that adds additional controls, extenders, and other client and server objects, provides a new unique way to develop .NET controls. It leverages the existing structures in JavaScript to create a wide array of add-ons that make developing JavaScript code easier than what it was.
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.
In this article, I plan to review some of the new features available in the JavaScript language that Microsoft added in. I should say now that the additions are not new coding conventions to JavaScript. Rather; all of the approaches utilize all of the existing development structures available in the JavaScript language. The client library works within these constructs to make additional coding styles available. Let’s first look at the available constructs.
One important note to the reader: the ASP.NET AJAX framework exists in both a server and client form. In this article, I’m mentioning the client form mostly, which I’ll refer to as the ASP.NET AJAX client library, client library, or JavaScript library.
A name=JavaScript_Objects>
JavaScript Objects
JavaScript is an object-oriented language, but the language makes it a challenge because it’s very flexible, while still being case-sensitive (which can cause object referencing issues due to typos). To create an object’s definition (or class) in JavaScript, use the following:
Listing 1: Creating Object Definitions in JavaScript
1.function Nucleo.JSObject() = { }
2.function Nucleo.JSObject.prototype = { }
The following code example defines a constructor for the Nucleo.JSObject object. JavaScript makes use of the new keyword to instantiate a new instance of the object’s definition. For performance reasons, it is recommended that all local variables be defined in the constructor’s definition, even if they are used in the prototype. In the .NET world, Nucleo would be the namespace and JSObject would be the class name; however, in plain-old JavaScript, this is defining one object as Nucleo.JSObject. Nucleo isn’t recognized as a namespace per-se.
In .NET, each class has a definition, where it defines properties, methods, and events. A similar approach for this exists in the ASP.NET AJAX framework through the use of a prototype. The prototype defines the class definition that a JavaScript object instance uses whenever it is instantiated through the constructor; the prototype construct is already supported in the JavaScript language.
By its very nature, JavaScript doesn’t really support properties or events the same way .NET does. In JavaScript, most structural definitions use methods; the constructor is essentially a method, and the definition is a method as well. In the examples included, we’re going to be essentially working with methods, which use different constructs about that method.
Let’s begin to look at these constructs.
In the Figure 1 example, the code sample illustrates the definition of a constructor and class using JavaScript without the ASP.NET AJAX framework. Let’s take a look at our first example of how the ASP.NET AJAX library extends this to include support for namespaces:
Listing 2: Class/Namespace Definition
01.Type.registerNamespace(“Nucleo”);
02.//constructor
03.function Nucleo.JSObject() { }
04.//class definition
05.Nucleo.JSObject.prototype =
06.{
07. //methods, properties, etc.
08.}
09.Nucleo.JSObject.registerClass(“Nucleo.JSObject”, Sys.Component);
Let’s look at what is new:
· Type – This object already exists in JavaScript, but has been extended to add support for the Microsoft approach to working with objects.
· registerNamespace - The registerNamespace attribute defines the Nucleo portion of Nucleo.JSObject as a namespace. More on this later.
· registerClass – Registers the Nucleo.JSObject definition as a class. The ASP.NET AJAX client library knows that this is a class, which is important for reasons I’ll explain later. One important note here: even though the Type object has a registerClass method, always use the registerClass method on the object itself (i.e. Nucleo.JSObject).
One item I didn’t include in the previous section was the use of Sys.Component. Sys.Component is a class that Nucleo.JSObject inherits from in this example (which most objects in the client library inherit from), although no inheritance is also allowed. The ASP.NET AJAX approach supports inheritance, as well as implementing interfaces. Both are applied using the registerClass method in the previous code segment; interfaces happen to be the third parameter (if supplied).
Let’s take a look at an interface; this implementation requires some methods be implemented by the class that consumes it:
Listing 3: Definition of a prototype
1.Type.registerNamespace(“Nucleo”);
2.function Nucleo.IJSObject(){ throw new Error.notImplemented(); }
3.Nucleo.IJSObject.prototype =
4.{
5. calculate : function(val1, val2) { throw new Error.notImplemented(); }
6.}
7.Nucleo.IJSObject.registerInterface(“Nucleo.IJSObject”);
Our Nucleo.JSObject class can now define Nucleo.IJSObject for the third parameter of the registerClass method, which states that the JSObject definition implements the required methods defined in IJSObject. In an interface definition, nothing gets implemented; rather, only the signature needs defined.
Note that the methods of the interface throw an exception. One of the new features added is the ability to throw specific errors using the Error object. The Error object defines a preexisting set of exceptions at your disposal, each with different sets of parameters, specifying the message, value in error, etc.
In JavaScript, the concept of a property is essentially a method with a get_ or set_ prefix. For instance, below is the definition of a property. Remember that previously I said that a property’s definition would be in the prototype.
Listing 4: Properties defined in prototype
1.get_text : function()
2.{
3. return this._text;
4.},
5.set_text : function(value)
6.{
7. this._text = value;
8.}
The body of the property getter/setter works with the _text local variable stored the underlying data. Remember that the _text variable is defined in the constructor. Using the property in JavaScript is shown below:
Listing 5: Working with the text property
1.var text = jsObjectInstance.get_text();
2.text = text + “some value”;
3.jsObjectInstance.set_text(text);
Although JavaScript uses the get_ and set_ prefixes, these are omitted when referencing them from the server-side code (we’ll talk about this later).
Let’s quickly look at enumerations, though I won’t delve too deeply into it:
Listing 6: Defining enumerations
01.Type.registerNamespace(“Nucleo”);
02.function Nucleo.JSEnumeration() { throw new Error.notImplemented(); }
03.Nucleo.JSEnumeration.prototype =
04.{
05. First : 1,
06. Second : 2,
07. Third : 3
08.}
09.Nucleo.JSEnumeration.registerEnum(“Nucleo.JSEnumeration”);
Values for the enumeration are in a key/value pair separated by a colon. This is pretty self explanatory, so I’ll skip the description.
Events are exposable through a new object: EventHandlerList. Luckily, any component inheriting from Sys.Component (which includes any custom AJAX control or extender) can use the get_events property and use the ASP.NET AJAX events architecture.
The EventHandlerList object works like a dictionary object. Let’s look at an example of how an event handler gets registered, unregistered, and raised. This is the typical approach used in the prototype definition:
Listing 7: Working with events
01.add_myEvent : function(handler)
02.{
03. this.get_events().addHandler(“myEvent”, handler);
04.},
05.remove_myEvent : function (handler)
06.{
07. this.get_events().removeHandler(“myEvent”, handler);
08.},
09.raise_myEvent : function ()
10.{
11. var handler = this.get_events().getHandler(“myEvent”);
12. if (handler != null)
13. handler(this, Sys.EventArgs.Empty);
14.}
As you can see, events use the same concept of defining a handler for an event; however, the approach taken to register for events is different. An event is similar to a dictionary, where the event is simply a string name in the dictionary, and the handler as the value.
Events can be raised by getting an instance of the event, and triggering it, using the event signature. In the previous example, the myEvent event uses an object parameter (the sender or object raising the event), and an event argument (another new feature in the client library).
I intentionally left out methods in the ASP.NET AJAX architecture, simply because everyone who develops JavaScript code should know what a method, or function, is. Taking this a step further, it’s possible to define static methods on objects as well. To define a static method, assign the method to the object definition itself and not the prototype. Below is an example of a static method:
Listing 8: Static method definition and usage
1.function Nucleo.JSO { }
2.Nucleo.JSObject.parseJSON = function(text)
3.{
4. //Do something
5. return output;
6.}
7.var output = Nucleo.JSObject.parseJSON(“jsonText”);
By separating the definition of properties, and providing the registration of classes, namespaces, interfaces, etc, the ASP.NET AJAX framework makes JavaScript come closer to the .NET framework. Depending on your view of the .NET framework determines whether you believe that’s a good thing or a bad thing, but here’s why I believe it’s a good thing: it’s creating a managed object model client-side JavaScript code and for ASP.NET controls. In the perspective of custom controls, these controls can manage themselves on the client side as well as the server side.
There are many areas where these concepts can be applied to, not being limited to custom controls. Custom controls and extenders is a big topic that this can be applied to. In thinking about it from this aspect only, imagine your controls having the ability to tap into an event lifecycle on the client-side. Right now, many developers write manual JavaScript to change how the interface works. For instance, developers show or hide a control based upon a button click. This has to be coded manually, but in ASP.NET AJAX, this can be done using an extender, which makes the developer’s life easy. I’ve developed such a component with the greatest of ease.
This is the goal of the ASP.NET AJAX framework, along with the AJAX Data Controls framework (http://www.codeplex.com/AjaxDataControls) that you’ve seen discussed here on http://dotnetslackers.com/. Over the next few years, there is going to be a large shift in control development in the Microsoft realm, leveraging more of this control architecture on the client side, rather than the server side.
Let’s look at an example that puts this all in perspective. We are going to look at creating a component (that inherits from Sys.Component), and uses all of the features above. The script below starts by defining the namespace and class definition:
Listing 9: Base Definition
01.Type.registerNamespace(“Nucleo.Menus”);
02.function Nucleo.Menus.MenuItem(text)
03.{
04. Nucleo.Menus.MenuItem.initializeBase(this);
05. this._caption = null;
06. this._text = text;
07. this._visible = true;
08.}
09.Nucleo.Menus.MenuItem.prototype =
10.{
11. ..
12.}
13.Nucleo.Menus.MenuItem.registerClass(“Nucleo.Menus.MenuItem”, Sys.Component);
The prototype is filled in later. For now, we have a skeleton of a MenuItem class that represents an item in a menu (theoretically in a web control, but for now I’m working very generically). This class defines a constructor that takes a text definition to show as the menu item’s text. Notice the call to initializeBase; this method is similar to using MyBase.New() in VB.NET; it calls the base class’s constructor. This class also inherits from the Sys.Component base class, which is already defined in the client library.
I don’t know if you noticed, but in all my examples, the registerNamespace call is repeated over and over again. This is to be expected, and is the process that the ASP.NET AJAX framework, and the AJAX Control Toolkit, uses. Even though there are multiple registrations, this doesn’t cause a problem.
To expand on the class further, let’s add some properties to the prototype:
Listing 10: Property getters and setters
01.get_caption : function()
02.{
03. return this._caption;
04.},
05.set_caption : function(value)
06.{
07. if (this._caption != value)
08. {
09. this._caption = value;
10. this.raisePropertyChanged(“caption”);
11. }
12.},
13.get_text : function()
14.{
15. return this._text;
16.},
17.set_text : function(value)
18.{
19. if (this._text != value)
20. {
21. this._text = value;
22. this.raisePropertyChanged(“text”);
23. }
24.},
25.get_visible : function()
26.{
27. return this._visible;
28.},
29.set_visible : function(value)
30.{
31. If (this._visible != value)
32. {
33. this._visible = value;
34. this.raisePropertyChanged(“visible”);
35. }
36.}
Each property has a getter and setter. The getter simply returns the value, while the setter compares the value for differences and raises a property changed event when there are changes to the value (this method is defined in the Sys.Component class). Next, we move to methods. The following method is defined for the class:
Listing 11: Method definition
1.toggleVisibility : function()
2.{
3. if (this.get_visible() == true)
4. this.set_visible(false);
5. else
6. this.set_visible(true);
7.}
However, let’s add some event features to this component. Whenever the toggleVisibility method gets executed, the visibilityToggled event will fire. To do this, we need some methods to work with events. Remember that the Sys.Component base class defines a get_events() property. This property is of type EventHandlerList that contains all of the handlers for the respective event. Below are the methods that work with events. Again, these methods are defined in the prototype.
Listing 12: Event handling methods
01.add_visibilityToggled : function(handler)
02.{
03. this.get_events().addHandler(“visibilityToggled”, handler);
04.},
05.remove_visibilityToggled : function(handler)
06.{
07. This.get_events().removeHandler(“visibilityToggled”, handler);
08.},
09.raise_visibilityToggled : function()
10.{
11. var handler = this.get_events().getHandler(“visibilityToggled”);
12. if (handler != null)
13. handler(this, Sys.EventArgs.Empty);
14.}
Now, outside objects can register for the visibilityToggled event, and can act upon it. In this case, the toggleVisibility method can raise it using the following definition:
Listing 13: New toggleVisibility method that raises events
1.toggleVisibility : function()
2.{
3. if (this.get_visible() == true)
4. this.set_visible(false);
5. else
6. this.set_visible(true);
7. this.raise_visibilityToggled();
8.}
Let’s take this a step further by adding an interface. The following interface definition is below, and will be added to the registerClass call:
Listing 14: Interface definition
01.function Nucleo.Menus.IMenuItem()
02.{
03. throw Error.notImplemented();
04.}
05.Nucleo.Menus.IMenuItem.prototype =
06.{
07. get_text : function() { throw Error.notImplemented(); },
08. set_text : function(value) { throw Error.notImplemented(); },
09. get_visible : function() { throw Error.notImplemented(); },
10. set_visible : function(value) { throw Error.notImplemented(); },
11. toggleVisibility : function() { throw Error.notImplemented(); }
12.}
13.Nucleo.Menus.IMenuItem.registerInterface(“Nucleo.Menus.IMenuItem”);
This interface definition defines the same properties and method already defined in the MenuItem class. All that’s required of us is to add it to the class registration, shown below:
Listing 15: Adding the interface to the class registration
1.Nucleo.Menus.MenuItem.registerClass(“Nucleo.Menus.MenuItem”, Sys.Component,
2. Nucleo.Menus.IMenuItem);
And now we have an AJAX component that we can use in our .NET applications. To use this component in our ASP.NET pages, add the script to the ScriptManager’s script references, or include it with another component.
This article is the first of a series of AJAX introduction articles. The building blocks you’ve seen in this article are built upon in subsequent articles. The ASP.NET AJAX framework defines new constructs for use in custom development.