Chapter 4. Using ASP.NET AJAX JavaScript Extensions

In addition to delivering a considerable amount of Ajax functionality in an easy-to-use framework, ASP.NET AJAX provides a number of additions to JavaScript that can make client coding easier. Among these are OOP-style constructs, such as namespaces, inheritance, and interfaces, as well as client-side reimplementations that resemble .NET constructs such as StringBuilder. Also, selected JavaScript objects are enriched with new features.

ASP.NET AJAX Shortcuts and Helper Functions

By including the ASP.NET AJAX ScriptManager control into a web page, you automatically get a number of useful helper functions and shortcuts to important JavaScript features. Some of these new functions just save you some typing. Some of them, however, offer a much greater advantage: they are browser-agnostic. For instance, Internet Explorer on one side and all other modern browsers on the other side each provide their unique way to attach event listeners (see Chapter 2). The code in ASP.NET AJAX detects the browser type and automatically uses the appropriate function on every system.

Shortcuts

The method most often used by developers to create a modern JavaScript-powered web site is document.getElementById( ). Several Ajax toolkits provide a shortcut for this rather lengthy method name called $( ). ASP.NET AJAX tries to coexist with other frameworks and therefore is using a new name: $get( ).

Whereas this saves only a few characters, the new event handling helper functions are of greater value. When programmatically assigning a handler function to an event, you can use the $addHandler( ) function.

function $addHandler (element, eventName, handler) { }

You need to provide the element attribute to attach the handler to the eventName (without the “on” prefix!), and the actual handler (as a function reference or an anonymous function). Below is an example that pops up a warning window when a user clicks on a button:

$addHandler("Button1", "click", function(  ) { alert("Ouch!"); } );

Tip

When you want to assign handlers for several events for an element, you can either use several $addHandler( ) calls, or you use $addHandlers( ), providing the element and an array of events and handler functions as arguments.

To remove a specific handler, use the $removeHandler( ) function demonstrated here:

function $removeHandler(element, eventName, handler) {}

Note that you have to pass the event-handler function again when removing the handler. Therefore, it is more convenient in most cases to call the $clearHandlers( ) function, which removes all handlers for a given element:

function $clearHandlers(element) {}

The ASP.NET AJAX team tried very hard to recreate to a certain extent the ASP.NET page lifecycle in JavaScript. JavaScript itself only supports a load event, which is not enough for some applications. It also has a serious flaw: the event is fired when the HTML markup of the current page has been fully loaded. However, ASP.NET AJAX sites load several external JavaScript libraries. They are usually not available yet when the HTML has been fully rendered by the browser. Therefore, using the JavaScript load event to start any ASP.NET AJAX coding is too early in the client page lifecycle. The load event defined by ASP.NET AJAX only runs when all external JavaScript files have been fully loaded. In order to execute code after the event has been fired, you have two options. You can either use the Sys.Application.add_load( ) method (as described in the sidebar, "Adding Event Handlers, the Alternative Way“), or you can write a JavaScript function named pageLoad( ). When ASP.NET AJAX determines that all external files have been fully loaded, it executes the pageLoad( ) function, if it exists on the current page—quite similar to the way ASP.NET executes the server-side Page_Load( ) method if it exists. This method provides a safe way to start using ASP.NET AJAX as early as possible.

function pageLoad(  ) {
  /* ...*/
}

At the end of a page, when the user closes the browser or navigates to another URL, the unload event occurs. You can execute code when this happens by writing a function called pageUnload( ).

function pageUnload(  ) {
  /* ...*/
}

ASP.NET AJAX automatically executes such a function at the appropriate time, if it has been implemented.

DOM Element Methods

For DOM elements, ASP.NET AJAX provides special methods for common scenarios like applying CSS classes. These methods are defined in the Sys.UI.DomElement class. For common features like setting CSS classes or removing them, CSS class methods take some keyboarding weight off developers’ shoulders.

Sys.UI.DomElement.addCssClass(element, className)

Adds a CSS class (className) to an HTML element.

Sys.UI.DomElement.containsCssClass(element, className)

Checks whether the CSS class definition of an element contains a certain CSS class.

Sys.UI.DomElement.removeCssClass

Removes a CSS class (className) from an HTML element.

Sys.UI.DomElement.toggleCssClass(element, className)

Checks whether the CSS class definition of an element contains a certain CSS class (className). If it does, it removes this CSS class, otherwise it appends the CSS class. Apart from CSS classes, ASP.NET AJAX provides helper methods for some of the most often accessed properties of general HTML elements: width, height, and position.

Sys.UI.DomElement.getBounds(element)

Returns an object with the properties x, y, height, width, containing the x coordinate, y coordinate, height, and width of the given element.

Sys.UI.DomElement.getLocation(element)

Returns an object with the properties x and y, containing the x and y coordinates of the given element.

Sys.UI.DomElement.setLocation(element, x, y)

Sets the x and y coordinates of the given element.

Tip

Another method defined within Sys.UI.DomElement is getElementById( )—but you already know the shortcut for that, $get( ).

Extensions to Existing JavaScript Objects

Chapter 2 described how to add methods to JavaScript base objects like Date. This feature has also been used heavily by the ASP.NET AJAX developers. As a result, JavaScript base types in the following list have been enriched with additional features:

  • Array

  • Boolean

  • Date

  • Error

  • Number

  • Object

  • String

None of these extensions alone is worth writing home about, but taken together, they can provide some real value if you write a lot of JavaScript code. Remember that a key idea of any Ajax framework is to dramatically reduce the amount of custom JavaScript code that needs to be written on top of the framework.

Instead of a complete list, we’ll present just one easily written example. The new Array.forEach( ) method applies a function to each element of a given array.

function Array$forEach(a, fnct) {
  for (var i = 0; i = a.length; i++) {
      if (typeof(a[i]) != "undefined") {
        fnct.call(null, a[i], i, a);
    }
  }
}

However, the built-in ASP.NET AJAX forEach( ) method saves some typing, a bit of debugging, and a lot of extra maintenance work.

Tip

An excellent overview of the new JavaScript object properties are available at no cost as PDF and XPS files. See the "For Further Reading" section at the end of this chapter for details on how to get those files.

ASP.NET AJAX OOP Features for JavaScript

In Chapter 2, we learned JavaScript does have some OOP capabilities, but they are no match for those in programming languages like Visual Basic or C#. However, it’s relatively easy to add new features to JavaScript using JavaScript itself, something the ASP.NET AJAX team has exploited.

To facilitate OOP development, ASP.NET AJAX adds to JavaScript some OOP-type features, which are covered in this chapter. These include namespaces, abstract classes, and interfaces. The additional features are designed to help you design and write more structured client-side code. They can apply not only to Ajax applications, but also to any JavaScript code you write.

Namespaces

A key ASP.NET AJAX JavaScript OOP extension is the addition of namespace functionality. Namespaces enable you to encapsulate functionality into logical groups under a single name. They help avoid name collisions with functions that have the same name but fulfill different purposes. The JavaScript language specification does not specify namespaces, so the language itself cannot offer this functionality. However, ASP.NET AJAX uses a simple technique to emulate namespaces. You can create a new class (which serves as the “namespace”), then make another (new) class accessible as a property of the namespace class. This allows you to access your class using NamespaceClassName.YourClassName.

One of the base classes in ASP.NET AJAX runtime is the Type class. Two methods of this class come in handy when creating the ASP.NET AJAX namespaces:

Type.registerNamespace(name)

Registers a namespace

Class.registerClass(name, base type, interface type)

Registers a class as a member of the namespace

To demonstrate this technique, let’s create an OReilly namespace for a group of classes used in this book. Suppose that one of them is named Software with two properties: name and vendor. First, you must register the OReilly namespace:

Type.registerNamespace("OReilly");

Next, you create the Software class as a member of OReilly using the following code snippet:

OReilly.Software = function(name, vendor) {
  var _name = (name != null) ? name : "unknown";
  var _vendor = (vendor != null) ? vendor : "unknown";

  this.getName = function(  ) {
    return _name;
  }
  this.setName = function(name) {
    _name = name;
  }

  this.getVendor = function(  ) {
    return _vendor;
  }
  this.setVendor = function(vendor) {
    _vendor = vendor;
  }
}

Warning

The class constructor expects values for the two properties. To perform data hiding, the class member values are saved as separate variables, and the class implements setter and getter methods for the properties. Note that JavaScript does not support private or protected properties. Therefore, all class members are public. The data hiding implemented here does not provide protection from unauthorized access; it is just a helper tool to structure code and make the data access coherent. Of course most technologies that do support private or protected still allow access to those properties using reflection.

Finally, OReilly.Software must be registered as a class so that you can use it in your applications. You do this with the registerClass( ) method. This method can take up to three parameters:

name

The name of the class

base type

The base type of the class, if any, as a reference to the type

interface type

The interface type of the class, if any, as a reference to the type

The OReilly.Software class does not have a base type and does not implement an interface type. The call to registerClass( ) registers the class, omitting the second and third parameters:

Type.registerClass("OReilly.Software");

Tip

ASP.NET AJAX implements several types, but the one you will use most often is Sys.IDisposable (because you can write a dispose( ) method that is called automatically when the script ends), even though JavaScript has only a simple garbage collector. However, you do not necessarily need to implement an interface. If you do not use an interface, the call to Type.registerClass( ) is subsequently not necessary to access the new class. For more advanced features, this method call is mandatory (see the following sections).

Now, you can instantiate the Software class using the new keyword to get and set its properties. Example 4-1 creates two instances; one for Microsoft Internet Explorer and one for Mozilla Foundation Firefox. Example 4-1 also uses a very handy feature of ASP.NET AJAX. After both the page and all libraries used by ASP.NET AJAX have been fully loaded, the pageLoad( ) function is executed (if it exists on the page). Remember that window.onload does not take loading of external files like JavaScript libraries into account. Therefore you should always use pageLoad( ) when using ASP.NET AJAX for that task.

Example 4-1. Using ASP.NET AJAX namespaces
ClientNamespaces.aspx

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>ASP.NET AJAX</title>
  <script language="Javascript" type="text/javascript">
  function pageLoad(  ) {
    var s = "";

    Type.registerNamespace("OReilly");
    OReilly.Software = function(name, vendor) {
      var _name = (name != null) ? name : "unknown";
      var _vendor = (vendor != null) ? vendor : "unknown";

      this.getName = function(  ) {
        return _name;
      }
      this.setName = function(name) {
        _name = name;
      }

      this.getVendor = function(  ) {
        return _vendor;
      }
      this.setVendor = function(vendor) {
        _vendor = vendor;
      }
    }

    Type.registerClass("OReilly.Software");

    var ie = new OReilly.Software("Internet Explorer", "Microsoft");
    s = ie.getName() + " from " + ie.getVendor(  ) + "<br />";

    var ff = new OReilly.Software(  );
    ff.setName("Firefox");
    ff.setVendor("Mozilla Foundation");
    s += ff.getName() + " from " + ff.getVendor(  );

    document.getElementById("output").innerHTML = s;
  }
  </script>
</head>
<body>
  <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    <div id="output">
    </div>
  </form>
</body>
</html>

Figure 4-1 shows the result displayed when the page is loaded.

Instantiating two objects within the same namespace
Figure 4-1. Instantiating two objects within the same namespace

Although ASP.NET AJAX namespace classes are not real namespaces, they can make it easier for you to structure complex JavaScript code, with very little overhead.

Class Inheritance

As detailed in Chapter 2, the prototype property provides limited support for class inheritance in JavaScript. ASP.NET AJAX provides more abstraction. The prototype mechanism is supported for namespace classes that were registered using Class name.registerClass( ). As a second parameter for registerClass( ), you can specify a base class. Here is where you specify from which class the current class derives.

Derived classes

Let’s create a class that inherits from Software. One very specific type of software is a web browser, so let’s create a Browser class. In addition to the features of the generic Software class, a browser would benefit from some extra properties. An isJavaScriptSupported property can provide information about whether a particular browser is capable of running JavaScript.

OReilly.Browser = function(name, vendor, isJavaScriptSupported) {
  //...
}

Here’s how to register the class. Note how the new class (the string parameter) derives from the old OReilly.Software class (no string!).

OReilly.Browser.registerClass("OReilly.Browser", OReilly.Software);

Of course, it would be possible to create getter and setter methods for name and vendor once again, and to write the constructor code as well. However, one of the benefits of class inheritance (actually, the major benefit) is that you can reuse functionality. Because OReilly.Browser inherits from OReilly.Software, you can use the getter and setter methods (i.e., the properties) that are already there, as well as the _name and _vendor “private” members. You do, however, need to add getter and setter methods and private members for the new isJavaScriptSupported property, as shown here:

var _isJavaScriptSupported = (isJavaScriptSupported != null) ?
  isJavaScriptSupported : false;

this.getIsJavaScriptSupported = function(  ) {
  return _isJavaScriptSupported;
}
this.setIsJavaScriptSupported = function(isJavaScriptSupported) {
  _isJavaScriptSupported = isJavaScriptSupported;
}

All that remains is for us to write the constructor. But instead of writing it again from scratch, you can reuse the base class constructor. To do so, ASP.NET AJAX provides the initializeBase( ) method. The first parameter is the instance of which the base class will be initialized; usually, you provide this as the value. The second parameter is an array of arguments to be passed to the base constructor (the base constructor defines which arguments it expects). In our case, this array consists of the browser name and vendor.

OReilly.Browser.initializeBase(this, new Array(name, vendor));

Tip

You can save a few characters and use JSON to create the array:

OReilly.Browser.initializeBase(this, [name,vendor]);

Example 4-2 shows the code needed to create and use the new derived Browser class.

Example 4-2. Using ASP.NET AJAX class inheritance
ClientInheritance.aspx

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>ASP.NET AJAX</title>

  <script language="Javascript" type="text/javascript">
  function pageLoad(  ) {
    var s = "";

    Type.registerNamespace("OReilly");
    OReilly.Software = function(name, vendor) {
      var _name = (name != null) ? name : "unknown";
      var _vendor = (vendor != null) ? vendor : "unknown";

      this.getName = function(  ) {
        return _name;
      }
      this.setName = function(name) {
        _name = name;
      }

      this.getVendor = function(  ) {
       return _vendor;
      }
      this.setVendor = function(vendor) {
        _vendor = vendor;
      }
    }
    Type.registerClass("OReilly.Software");


    OReilly.Browser = function(name, vendor, isJavaScriptSupported) {
    OReilly.Browser.initializeBase(this, new Array(name, vendor));
      var _isJavaScriptSupported = (isJavaScriptSupported != null) ?
        isJavaScriptSupported : false;

      this.getIsJavaScriptSupported = function(  ) {
        return _isJavaScriptSupported;
      }
      this.setIsJavaScriptSupported = function(isJavaScriptSupported) {
        _isJavaScriptSupported = isJavaScriptSupported;

      }

    }
    OReilly.Browser.registerClass("OReilly.Browser", OReilly.Software);
    var ie = new OReilly.Browser("Internet Explorer", "Microsoft", true);
    s = ie.getName() + " from " + ie.getVendor(  ) +
      (ie.getIsJavaScriptSupported(  ) ? " (w/ JS)" : " (w/o JS)") +
      "<br />";

    var lynx = new OReilly.Browser("Lynx");
    lynx.setIsJavaScriptSupported(false);
    s += lynx.getName() + " from " + lynx.getVendor(  ) +
      (lynx.getIsJavaScriptSupported(  ) ? " (w/ JS)" : " (w/o JS)");

    document.getElementById("output").innerHTML = s;
  }
  </script>

</head>
<body>
  <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    <div id="output">
    </div>
  </form>
</body>
</html>

Figure 4-2 shows the results displayed when the page is loaded and its JavaScript runs.

Instantiating objects derived from the same base class
Figure 4-2. Instantiating objects derived from the same base class

Tip

Just in case you are wondering, the Lynx text browser does have a “vendor.” The copyright holder is the University of Kansas.

Accessing base methods

When we talk about class inheritance, a logical question is whether methods can be overridden in derived classes. The answer is yes. The next question: is there any way to access the equivalent method of the base class, (i.e., the overridden method)? Even better, the answer is again yes, ASP.NET AJAX allows you to do so. To demonstrate this, let’s add a toString( ) method to OReilly.Software that outputs the product and vendor names stored by the class. The prototype property ensures automated inheritance and also helps demonstrate access to the base method later on.

OReilly.Software.prototype.toString = function(  ) {
  return this.getName() + " from " + this.getVendor(  );
}

Tip

You could also directly access the properties _name and _vendor as variables. Using the getter methods is just a personal preference. There is no functional difference in doing so.

In the OReilly.Browser class, you could write a similar toString( ) method:

OReilly.Browser.prototype.toString = function(  ) {
  return this.getName() + " from " + this.getVendor(  ) +
         (this.getIsJavaScriptSupported(  ) ? " (w/ JS)" : " (w/o JS)");
}

However, it is once again advisable to reuse existing code, in this case, the base class’s toString( ) method. ASP.NET AJAX provides you with callBaseMethod( ), a helper method to call a method from the parent class that can take up to three parameters:

instance

The instance whose parent’s method to call (usually this)

methodName

The name of the method (as a string)

baseArguments

Parameters for the method, if any (as an array)

In this case, the toString( ) method of OReilly.Browser can be implemented as the following code demonstrates:

OReilly.Browser.prototype.toString = function(  ) {
  return OReilly.Browser.callBaseMethod(this, "toString") +
         (this.getIsJavaScriptSupported(  ) ? " (w/ JS)" : " (w/o JS)");
}

Now, the code to output the browser information can be reduced a bit to these commands below:

var s = "";
var ie = new OReilly.Browser("Internet Explorer", "Microsoft", true);
s = ie.toString(  ) + "<br />";
var lynx = new OReilly.Browser("Lynx", null, false);
s += lynx.toString(  );
document.getElementById("output").innerHTML = s;

Example 4-3 shows the complete listing.

Example 4-3. Accessing a base class method
ClientBaseMethods.aspx

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>ASP.NET AJAX</title>

  <script language="Javascript" type="text/javascript">
  function pageLoad(  ) {
    var s = "";

    Type.registerNamespace("OReilly");
    OReilly.Software = function(name, vendor) {
      var _name = (name != null) ? name : "unknown";
      var _vendor = (vendor != null) ? vendor : "unknown";

      this.getName = function(  ) {
        return _name;
      }
      this.setName = function(name) {
        _name = name;
      }

      this.getVendor = function(  ) {
        return _vendor;
      }
      this.setVendor = function(vendor) {
        _vendor = vendor;
      }
    }
    Type.registerClass("OReilly.Software");

    OReilly.Browser = function(name, vendor, isJavaScriptSupported) {
      OReilly.Browser.initializeBase(this, new Array(name, vendor));
      var _isJavaScriptSupported = (isJavaScriptSupported != null) ?
isJavaScriptSupported : false;
      this.getIsJavaScriptSupported = function(  ) {
        return _isJavaScriptSupported;
      }
      this.setIsJavaScriptSupported = function(isJavaScriptSupported) {
        _isJavaScriptSupported = isJavaScriptSupported;
      }
    }
    OReilly.Browser.registerClass("OReilly.Browser", OReilly.Software);

    OReilly.Software.prototype.toString = function(  ) {
      return this.getName() + " from " + this.getVendor(  );
    }
    OReilly.Browser.prototype.toString = function(  ) {
      return OReilly.Browser.callBaseMethod(this, "toString") +
        (this.getIsJavaScriptSupported(  ) ? " (w/ JS)" : " (w/o JS)");
    };

    var ie = new OReilly.Browser("Internet Explorer", "Microsoft", true);
    s = ie.toString(  ) + "<br />";

    var lynx = new OReilly.Browser("Lynx", null, false);
    s += lynx.toString(  );

    document.getElementById("output").innerHTML = s;
  }
  </script>

</head>
<body>
  <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    <div id="output">
    </div>
  </form>
</body>
</html>

Note that when you run this page, the output of this code is identical to that shown in Figure 4-2.

Interfaces

The final OOP-like feature made available to JavaScript by ASP.NET AJAX is interfaces. An interface does not contain any implementation at all but instead specifies the members that subclasses must implement. Even if you inherit from an interface, there is no implementation you can use. Instead, you must create the methods that are defined in the interface. This is a good way for developers to keep class structure and implementation details separated in their code.

As you have probably already guessed, the method for creating an interface is Type.registerInterface( ). The interface name you just created is provided as the third (optional) parameter of registerClass( ). So, starting with the interface itself, we will use the following code:

OReilly.IProduct = function(  ) {
  this.toString = Function.abstractMethod;
}
Type.registerInterface("OReilly.IProduct");

Here, OReilly.Product is an abstract class. Unfortunately, the final version of ASP.NET AJAX does not support abstract classes (pre-release versions did). Therefore, there is no technical difference between abstract classes and regular classes.

In the following example, the OReilly.Product class introduces and implements the properties name and vendor.

OReilly.Product = function(name, vendor) {
  var _name = (name != null) ? name : "unknown";
  var _vendor = (vendor != null) ? vendor : "unknown";

  this.getName = function(  ) {
    return _name;
  }
  this.setName = function(name) {
    _name = name;
  }
  this.getVendor = function(  ) {
    return _vendor;
  }
  this.setVendor = function(vendor) {
    _vendor = vendor;
  }
}
Type.registerClass("OReilly.Product");

The next class to be implemented is OReilly.Software. Since we do not want to instantiate this class directly (we have subclasses like OReilly.Browser for that), this can now also be turned into an abstract class. It derives from OReilly.Product (to get name and vendor), but it also implements OReilly.IProduct (for the toString( ) method). After declaring the class, we register it with the following call to Type.registerClass( ):

OReilly.Software.registerClass("OReilly.Software", OReilly.Product,
OReilly.IProduct);

The rest of the code remains unchanged. It is quite long, so you might consider putting it into an external .js file for legibility of the .aspx file. Example 4-4 shows the complete listing.

Example 4-4. Using interfaces to structure code
ClientInterface.aspx

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>ASP.NET AJAX</title>

  <script language="Javascript" type="text/javascript">
  function pageLoad(  ) {
    var s = "";

    Type.registerNamespace("OReilly");

    OReilly.IProduct = function(  ) {
      this.toString = Function.abstractMethod;
    }
    Type.registerInterface("OReilly.IProduct");

    OReilly.Product = function(name, vendor) {
      var _name = (name != null) ? name : "unknown";
      var _vendor = (vendor != null) ? vendor : "unknown";

      this.getName = function(  ) {
        return _name;
      }
      this.setName = function(name) {
        _name = name;
      }
      this.getVendor = function(  ) {
        return _vendor;
      }
      this.setVendor = function(vendor) {
        _vendor = vendor;
      }
    }
    Type.registerClass("OReilly.Product");

    OReilly.Software = function(name, vendor) {
      var _name = (name != null) ? name : "unknown";
      var _vendor = (vendor != null) ? vendor : "unknown";
      this.getName = function(  ) {
        return _name;
      }
      this.setName = function(name) {
        _name = name;
      }
      this.getVendor = function(  ) {
        return _vendor;
      }
      this.setVendor = function(vendor) {
        _vendor = vendor;
      }
    }
      OReilly.Software.registerClass("OReilly.Software",
OReilly.Product, OReilly.IProduct);
      OReilly.Software.prototype.toString = function(  ) {
      return this.getName() + " from " + this.getVendor(  );
    }

    OReilly.Browser = function(name, vendor, isJavaScriptSupported) {
      OReilly.Browser.initializeBase(this, new Array(name, vendor));
      var _isJavaScriptSupported = (isJavaScriptSupported != null) ? vendor : false;
      this.getIsJavaScriptSupported = function(  ) {
        return _isJavaScriptSupported;
      }
      this.setIsJavaScriptSupported = function(isJavaScriptSupported) {
        _isJavaScriptSupported = isJavaScriptSupported;
      }
    }
    OReilly.Browser.registerClass("OReilly.Browser", OReilly.Software);
    OReilly.Browser.prototype.toString = function(  ) {
      return OReilly.Browser.callBaseMethod(this, "toString") +
             (this.getIsJavaScriptSupported(  ) ? " (w/ JS)" : " (w/o JS)");
    }

    var ie = new OReilly.Browser("Internet Explorer", "Microsoft", true);
    s = ie.toString(  ) + "<br />";
    var lynx = new OReilly.Browser("Lynx", null, false);
    s += lynx.toString(  );
    document.getElementById("output").innerHTML = s;
  }
  </script>

</head>
<body>
  <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    <div id="output">
    </div>
  </form>
</body>
</html>

Client Versions of .NET Classes

In addition to adding OOP-like features for JavaScript coding, ASP.NET AJAX achieves two goals through client class implementions that are analogs of some .NET classes:

  • Functionality missing in JavaScript is provided as part of ASP.NET AJAX.

  • .NET developers with little JavaScript experience can use some familiar elements in their code.

In my opinion, this is one of the areas where upcoming ASP.NET AJAX versions will most certainly add more features, so the following list of classes is neither exhaustive nor final. Two useful features that are already available are Sys.StringBuilder and enumerations.

Sys.StringBuilder

One of the new features introduced in .NET 1.0 that really improved performance was the introduction of the StringBuilder class. The downside, however, is that applications are usually full of code such as that illustrated below:

string s = "", t;
while (  ) {
  t = <value>;
  s += t;
}

The problem lies in the statement s += t, which is equivalent to s = s + t. Whenever this code is executed, a copy of s and a copy of t are created in memory, concatenated, then saved back into s. However, it’s inefficient to create a copy of s to achieve these results. Therefore, StringBuilder uses an optimized algorithm for string concatenation.

In JavaScript, this approach does not have any measurable effect on memory (in fact, the implementation seems to be a tick slower than the standard approach). Then again, performance is not as critical an issue for client script as it is for server code. Nevertheless, for consistency with your server coding techniques, you can rely on your knowledge of .NET coding techniques and use StringBuilder on the client. Example 4-5 puts the StringBuilder class to work. It concatenates some strings to build an HTML chessboard.

Example 4-5. Using an ASP.NET AJAX StringBuilder
ClientStringBuilder.aspx

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>ASP.NET AJAX</title>

  <script language="Javascript" type="text/javascript">
  window.onload = function(  ) {
    var sb = new Sys.StringBuilder(  );
    for (var i = 8; i >= 1; i--) {
      for (var j = 97; j <= 104; j++) {
        sb.append(String.fromCharCode(j));
        sb.append(i);
        sb.append(" ");
      }
      sb.appendLine(  );
      sb.appendLine(  );
    }
    document.getElementById("output").innerHTML = "<pre>" + sb.toString(  )
+ "</pre>";
  }
  </script>

</head>
<body>
  <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    <div id="output"></div>
  </form>
</body>
</html>

The built-in JavaScript function String.fromCharCode( ) converts an ASCII code to its associated character, so the inner for loop runs from "a" through "h". As Figure 4-3 reveals, the code in Example 4-7 creates a simple chessboard.

A chessboard (with some potential)
Figure 4-3. A chessboard (with some potential)

Enumerations

Another .NET type that is emulated by ASP.NET AJAX for JavaScript is Enum. You can create a custom enumeration using the createEnum( ) method. The API for this changed quite a bit during the Atlas and ASP.NET AJAX development cycle. In its current form, you can create an enumeration as shown in the following listing, but you cannot iterate over it. You can create a namespace, if you wish to use one:

Type.registerNamespace("ORA.MyEnums");

Then, create the enum object, assigning it an (empty) function:

ORA.MyEnums.Ajax = function(  ) {};

Next, define all values in the enumeration, using the syntax below:

ORA.MyEnums.Ajax.prototype = {
  "Asynchronous": 0,
  "JavaScript": 1,
  "and": 2,
  "XML": 3
};

Finally, the enumeration needs to be registered:

ORA.MyEnums.Ajax.registerEnum("ORA.MyEnums.Ajax");

Example 4-6 shows a complete example that creates the enumeration and then accesses it.

Example 4-6. Using an ASP.NET AJAX Enum
ClientEnum.aspx

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>ASP.NET AJAX</title>

  <script language="Javascript" type="text/javascript">
  function pageLoad(  ) {
    Type.registerNamespace("ORA.MyEnums");
    ORA.MyEnums.Ajax = function(  ) {};
    ORA.MyEnums.Ajax.prototype = {
      "Asynchronous": 0,
      "JavaScript": 1,
      "and": 2,
      "XML": 3
    };
    ORA.MyEnums.Ajax.registerEnum("ORA.MyEnums.Ajax");

    document.getElementById("output").innerHTML +=
      ORA.MyEnums.Ajax.Asynchronous + " " +
      ORA.MyEnums.Ajax.JavaScript + " " +
      ORA.MyEnums.Ajax.and + " " +
      ORA.MyEnums.Ajax.XML;
  }
  </script>

</head>
<body>
  <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    <div id="output"></div>
  </form>
</body>
</html>

This code outputs the string "0 1 2 3" (the keys for the enumeration entries) in the <div> element.

Enumerations are also used internally by ASP.NET AJAX to define mouse button values (the following code snippet has been edited and reformatted for clarity).

Sys.UI.MouseButton = function(  ) { };
Sys.UI.MouseButton.prototype = {
  leftButton:0,
  middleButton:1,
  rightButton:2
};
Sys.UI.MouseButton.registerEnum("Sys.UI.MouseButton");

Summary

The ASP.NET AJAX client script library implements several convenient features not present in standard JavaScript, including OOP-like functionality and client-side equivalents of .NET Framework features. These features can be used by any JavaScript programmer, without repercussions to ASP.NET or the server-side features of ASP.NET AJAX.

For Further Reading

http://www.kevlindev.com/tutorials/javascript/inheritance

Online tutorial for JavaScript’s OOP capabilities

http://aspnetresources.com/blog/ms_ajax_cheat_sheets_batch2.aspx

“Cheat sheets” for ASP.NET AJAX’s JavaScript extensions

http://ajax.asp.net/docs/clientreference/global/default.aspx

Documentation for helper functions and JavaScript base type extensions

http://quickstarts.asp.net/futures/ajax/doc/cssselectors.aspx

The ASP.NET AJAX Futures provides JavaScript helper functions to select elements based on CSS rules

Get Programming ASP.NET AJAX now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.