This chapter is about the future of building web applications. In this chapter, you learn how to build “pure” Ajax applications that execute on the browser instead of the server. In the first part of this chapter, you learn how Microsoft has extended JavaScript so that the JavaScript language more closely resembles .NET languages such as C# and VB.NET. You also learn how Microsoft has added useful debugging and tracing support to JavaScript. Next, we get to the heart of client-side AJAX. You learn how to perform Ajax calls from the browser to the server. You learn how to call both web methods exposed by a web service and web methods exposed by a page. We also discuss how you can call three built-in services included with the AJAX Framework. You learn how to work with the Authentication, Role, and Profile services. Finally, you learn how to create client-side AJAX controls and behaviors. You learn how to add client-side controls and behaviors to an AJAX page both programmatically and declaratively.
Making JavaScript Look Like C# Let me start by saying that there is nothing wrong with JavaScript the language. It is not a toy language. It is not a limited language. JavaScript simply has it roots in a different programming language family than other languages you are familiar with, such as C# and VB.NET.
NOTE For a great, quick introduction to JavaScript the language, I recommend that you read “A re-introduction to JavaScript” at http://developer.mozilla.org/en/docs/A_re-introduction_to_JavaScript.
JavaScript is an object-oriented programming language. In fact, one could argue that it is more object-oriented than languages such as C# and VB.NET. In a language such as C#, you make a distinction between classes (Aristotelian forms) and objects (Aristotelian matter). An object is an instance of a class, but a class does not exist in its own right. In JavaScript, classes do not exist. The only thing that exists are objects (everything is matter). Objects are not related to one another by being instances of the same class. Instead, one object can be related to another object when one object is the prototype for another object. Another major difference between JavaScript and C# is that JavaScript is a dynamic language. The type of a variable can change at any moment during runtime. When JavaScript code is executed, a String might transform into an Integer and back again. The C# language, on the other hand, is statically typed. Once declared, a String is a String and it can never be anything else. In the past, I believe that there were two reasons that JavaScript was not taken very seriously as a programming language. First, it seemed to be a dead language. If it was evolving, no one was paying attention. This situation changed when Mozilla Firefox was introduced into the world. New versions of JavaScript have been popping up in new releases of Firefox. It is no coincidence that the inventor of JavaScript, Brendan Eich, is now the CTO of Mozilla. Second, for a number of years, JavaScript was primarily used to create cheesy special effects on web pages or obnoxious advertisements that floated across web pages. All the excitement over Ajax, however, has caused developers to reexamine JavaScript as a language. Developers have learned that JavaScript is a much more flexible and powerful language than originally thought. For example, Douglas Crockford writes the following in “A Survey of the JavaScript Programming Language” (http://javascript.crockford.com/survey.html): When JavaScript was first introduced, I dismissed it as being not worth my attention. Much later, I took another look at it and discovered that hidden in the browser was an excellent programming language. My initial attitudes were based on the initial positioning of JavaScript by Sun and Netscape. They made many misstatements about JavaScript in order to avoid positioning JavaScript as a competitor to Java. Those misstatements continue to echo in the scores of badly written JavaScript books aimed at the dummies and amateurs market.
NOTE Several good articles on the evolution of the perception of JavaScript as a toy language to a real language can be found at the Ajaxian website (www.ajaxian.com).
So, it is with mixed feelings that I describe Microsoft’s attempts to make JavaScript work more like C#. On the one hand, I really don’t think the language needs improving. On the other hand, one of the best features of JavaScript is that you can easily extend it. So, if Microsoft can make JavaScript work more like C#, and be more palatable to .NET developers, who can blame them?
Using the Microsoft AJAX Library The supporting code for the client-side Microsoft AJAX Framework is contained in a single JavaScript file named MicrosoftAjax.js. This file is included in a page automatically when you add an ASP.NET ScriptManager control to a page. If you add an AJAX Web Form to a website within Visual Web Developer, the page contains the ScriptManager control automatically (see Figure 33.1). FIGURE 33.1 Adding an AJAX Web Form with Visual Web Developer.
NOTE By default, the ScriptManager adds references to both the MicrosoftAjax.js and MicrosoftAjaxWebForms.js files. The MicrosoftAjaxWebForms.js file contains the JavaScript code for supporting the UpdatePanel control. If you are not using the UpdatePanel control, you can assign the value false to the ScriptManager control’s EnablePartialRendering property and the MicrosoftAjaxWebForms.js file will no longer be included.
If you want to look at the contents of the MicrosoftAjax.debug.js file, you can open it from the Microsoft folder on the CD that accompanies this book.
The MicrosoftAjax.js file has a debug version and a release version. The release version is minimized and has debugging code removed so that it can be downloaded faster to a web browser. The size of the release version of the Microsoft AJAX Library is a trim 83KB. The size of the debug version is 325KB.
NOTE For good reason, people worry about the size of AJAX libraries. The Microsoft AJAX Library is extremely tiny. For the sake of comparison, the size of the release version of the Microsoft AJAX Library is only about double the size of a typical picture that is displayed on the home page of The New York Times website (www.nytimes.com). Furthermore, it is important to remember that a browser caches a JavaScript file across multiple pages and multiple visits to the same website.
All the functionality we discuss in the following sections is contained in the JavaScript code in the MicrosoftAjax.js file.
Creating an AJAX Client Library Before we do anything else, we need to discuss how you create an external JavaScript file and reference it in an AJAX Web Form page. You create a JavaScript file by selecting the menu option Website, Add New Item and selecting the AJAX Client Library option (see Figure 33.2). For example, the file in Listing 33.1 contains a single JavaScript function called sayMessage() that displays a JavaScript alert with a message. FIGURE 33.2 Creating an AJAX Client Library with Visual Web Developer.
You should notice two special things about this file. First, at the top of the file is a comment. The significance of this comment will be explained in the next section. Second, at the bottom of the file is a conditional that checks whether a namespace named Sys exists. If it does exist, the Application.notifyScriptLoaded() method is called. You should add this conditional to the bottom of every external JavaScript file that you use with the ASP.NET AJAX Framework. The notifyScriptLoaded() method is used to tell the Framework that the external JavaScript file has been successfully loaded. The check for the Sys namespace enables you to use the JavaScript file in applications that do not use the ASP.NET AJAX Framework. If the Sys namespace doesn’t exist, the application is not an ASP.NET AJAX application and there is no point in calling the notifyScriptLoaded() method. After you create an external JavaScript file, you can use it in an ASP.NET AJAX–enabled page by creating a script reference. The page in Listing 33.2 illustrates how you add a script reference to the myLibrary.js library. The page in Listing 33.2 contains a ScriptManager control that contains a <Scripts> sub-element. A reference to the myLibrary.js JavaScript library is contained in this sub-element. The page also contains a client-side pageLoad event handler. This event handler calls the sayMessage() function we defined in the external JavaScript file.
Taking Advantage of JavaScript Intellisense One huge advantage of using Visual Web Developer to write client-side AJAX applications is its support for Intellisense for JavaScript. The Intellisense appears both for built-in JavaScript objects and methods and for JavaScript libraries you write. Visual Web Developer will attempt to infer the data type of a JavaScript variable. This is quite an accomplishment considering that JavaScript is a dynamic language and a variable might change its data type at any time at runtime. For example, suppose you assign a string to a variable and declare it like this: var message = “hello world”;
Visual Web Developer will pop up with a list of all the string methods and properties when you type the variable name followed by a period (see Figure 33.3). FIGURE 33.3 Viewing Visual Web Developer JavaScript Intellisense.
Visual Web Developer can even detect whether you are attempting to work with a DOM element and will show the appropriate properties and methods.
NOTE The exact list of properties and methods displayed by Intellisense for a DOM element depends on your Validation settings (select the menu option Tools, Options, Text Editor, HTML, Validation). By default, Visual Web Developer is configured to validate against the XHTML 1.0 Transitional standard. Because the innerHTML property is not supported by that standard, you won’t get the innerHTML property in Intellisense. If, however, you target Internet Explorer 6.0 for validation, the innerHTML property appears.
When you create a new AJAX Client Library file, as we did in the previous section, the top of the file includes the following comment: ///
This strange-looking comment has a very important purpose. It enables Visual Web Developer to provide Intellisense for the Microsoft AJAX Library. For example, if you type Sys.Browser, you’ll see all the properties of the Browser object contained in the Sys namespace (see Figure 33.4). The Browser object is defined in the MicrosoftAjax.js file. You can provide Intellisense for a client library that you create. To add Intellisense to your libraries, you simply need to add XML comments to your JavaScript code (just like you would for C# or VB.NET code). For example, the client library in Listing 33.3 contains a method named addNumbers() that adds two numbers together. Notice that XML comments are used to describe the function and the two parameters. FIGURE 33.4 Displaying Intellisense for an external library. There is one important difference between how you add XML comments to JavaScript and how you add comments to C# or VB.NET. Notice that the comment appears within the function and not above the function. This is a requirement. After you add XML comments to a client library, the comments appear when you use the library. The comments appear in a page when you use the library in a page, and they appear in a separate client library when you add a reference the original library. For example, the file in Listing 33.4 contains a reference (at the top of the file) to the MathLib.js file. If you start typing the name of the addNumbers() function, Intellisense will appear (see Figure 33.5). FIGURE 33.5 Generating Intellisense with XML comments.
Notice that the reference to the MicrosoftAjax.js library uses the name attribute and that the reference to the mathLib.js library uses the path attribute. The MicrosoftAjax.js library is compiled as an embedded resource into the System.Web.Extensions.dll assembly. When a library is embedded in an assembly, you use the library name instead of its path.
NOTE For more information about JavaScript XML comments, see http://weblogs.asp.net/bleroy/archive/2007/04/23/the-format-for-javascript-doc-comments.aspx.
Working with Classes As discussed previously, the JavaScript language does not have classes. In the JavaScript universe, everything is an object. In order to make JavaScript look more like C#, Microsoft has extended JavaScript in such a way that you can pretend that the JavaScript language has classes. That way, you can use familiar C# language constructs such as interfaces and class inheritance. The file in Listing 33.5 demonstrates how you create a class by using the Microsoft AJAX Library. In JavaScript, you create a particular type of object by creating a constructor function. Just like in C# or VB.NET, a constructor function is typically used to initialize the fields of the object being created. The following code declares the constructor function for MyClass: var MyClass = function() {
this._message = ‘Hello World’; };
This constructor initializes a single private field named _message. The underscore is used to mark the field as a private field.
NOTE By convention, when building an ASP.NET AJAX application, you name private members of an object with a leading underscore. Any field, property, or method of an object that has a name starting with an underscore does not appear in Intellisense.
Strictly speaking, these object members are not truly private. From the point of view of the JavaScript language, there is no difference between an object member named _message and an object member named message. Calling alert(obj._message) will show the value of the private _message field. The JavaScript language does support truly private fields if you are willing to do some work (see http://www.crockford.com/javascript/private.html). Next, the public methods and properties of the class are defined by specifying a prototype object for the class. The following code causes all MyClass objects to include a property named message and two methods named sayMessage() and yellMessage(): MyClass.prototype = { get_message: function() { return this._message; }, set_message: function(value) { this._message = value; }, sayMessage: function() { alert(this._message); }, yellMessage: function() { alert(this._message + ‘!’); } };
Notice how you define a property. The message property is defined by creating a set_message() property setter and a get_message() property getter. The version of JavaScript included with Internet Explorer does not support true properties. So, properties are simulated by creating setter and getter methods.
NOTE Getters and setters were added to the JavaScript language in version 1.5. Because Internet Explorer does not support this feature, the getters and setters must be simulated with methods.
The two methods are declared in a straightforward manner. Both the sayMessage() and yellMessage() methods access the private _message field. The sayMessage() method displays the message in a JavaScript alert. The yellMessage() method tacks an exclamation mark at the end of the message before displaying it. Finally, the class is registered with the ASP.NET AJAX Framework with the following call to the registerClass() method: MyClass.registerClass(‘MyClass’);
This syntax here is a little odd. Notice that the registerClass() method is called on the class itself and that the name of the class is passed to the method. It all seems very circular. How can MyClass have a registerClass() method when it hasn’t even been registered yet? It all makes sense when you realize that MyClass is actually a function. Remember, MyClass is the constructor function for creating MyClass objects. The AJAX Library extends the JavaScript Function object so that it includes a registerClass() method. Therefore, the registerClass() statement isn’t really circular because the registerClass() method of the Function object is being called.
NOTE The registerClass() method is a method of the Type object. However, the Type object is simply an alias for the built-in JavaScript Function object. In the AJAX Library, this is accomplished with the following lines of code:
// Alias Function as Type window.Type = Function;
After you define a class, you can use it in pages and other external JavaScript files (client libraries). The page in Listing 33.6 creates an instance of the MyClass class in the pageLoad handler, assigns a value to the message property, and calls the yellMessage() method. When using the MyClass object, you get full Intellisense. The private _message field is hidden and the public methods are displayed (see Figure 33.6). FIGURE 33.6 Viewing MyClass members.
Working with Inheritance If you use the registerClass() method to simulate creating .NET classes on the client, you can take advantage of something that resembles class inheritance. You can create a derived class that inherits from a base class. The derived class can override and extend the properties and methods of the base class. For example, the client library in Listing 33.7 includes a Product class and a Laptop class. The Laptop class inherits from the Product class and overrides two of the base class properties. The Computer class (the base class) is registered with the following statement: Computer.registerClass(“Computer”);
The Laptop class (the derived class) is registered with the following statement: Laptop.registerClass(“Laptop”, Computer);
The second parameter passed to the registerClass() method represents a base class. This register statement causes the Laptop class to inherit from the Product class. Notice that the Laptop class includes a call to initializeBase() in its constructor function. This call is necessary to initialize the fields in the base class constructor function. In this case, if you neglect to all the initializeBase() method, the _price field won’t be initialized. The Laptop class contains two properties that override properties of the base Product class. The first property simply overrides the get_name() property. The second property, the get_price() property, also overrides the base property. However, this property calls the base property with the callBaseMethod() method so that the base price can be doubled (laptop computers always cost double the price of a normal computer). The page in Listing 33.8 illustrates how you can use the Laptop class. In the pageLoad() method, an instance of the Laptop class is created. Next, the static String.Format() method is used to build a string from the name and price properties of Laptop. Finally, the string is displayed in a JavaScript alert.
NOTE The Microsoft AJAX Library also supports creating interfaces and enumerations. To learn more, see the Microsoft .NET Framework SDK documentation.
Working with Namespaces In the .NET Framework, namespaces are used to group related classes. For example, all the classes related to working with the file system are located in the System.IO namespace. This is done for the purposes of documentation and to prevent naming collisions. If a class appears in the System.IO namespace, you know that is has something to do with file access. Different namespaces can have classes with the very same name. JavaScript does not explicitly support namespaces. The AJAX Library extends the JavaScript language so that you can simulate namespaces. For example, the page in Listing 33.9 registers a new namespaces named MyNamespace and adds a class named MyClass to the MyNamespace. The new namespace is created by calling the static Type.registerNamespace() method. Notice that when the class is registered with the registerClass() method, the fully qualified name of the class is used (MyNamespace.MyClass instead of MyClass). The page in Listing 33.10 demonstrates how you can use the MyNamespace namespace when working with a class. In Listing 33.10, an instance of the MyClass class is created in the pageLoad() method. The instance of the class is created by using the namespace-qualified name of the class: MyNamespace.MyClass().
Retrieving DOM Elements One of the most common operations you perform when building Ajax applications is retrieving DOM elements. For example, you might need to grab a span element from a page and modify its innerHTML. In a typical JavaScript application, you would use the document.getElementById() method to grab the DOM element, like this: var span = document.getElementById(“mySpan”); span.innerHTML = “Hello World!”;
The Microsoft AJAX Library introduces a shortcut method you can use instead of the document.getElementById() method. You can use the $get() method, like this: var span = $get(“mySpan”); span.innerHTML = “Hello World!”;
Alternatively, if you want to write really condensed code, you can use the $get() method, like this: $get(“mySpan”).innerHTML = “Hello World!”;
When calling $get(), you can pass a second parameter that represents the DOM element to search. For example, the following statement returns the mySpan element contained in the myDiv element: var myDiv = $get(“myDiv”); $get(“mySpan”, myDiv).innerHTML = “Hello World!”;
Be careful when calling either $get() or document.getElementById() because they are expensive operations in terms of performance. It is better to use $get() to grab a reference to a DOM element and assign it to a variable once than to use $get() multiple times to work with the same DOM element.
NOTE The Prototype framework (a popular, non-Microsoft AJAX framework) first introduced the $() function as an alias for document.getElementById(). Originally, Microsoft used $() instead of $get() as well. However, Microsoft wanted developers to be able to mix Prototype applications with Microsoft AJAX applications so it changed the name of the function to $get().
Handling DOM Events One of the biggest pains associated with writing client-side applications has to do with handling DOM events (for example, handling a client-side Button click event). The problem is that Microsoft Internet Explorer does not follow W3C standards. Therefore, you always end up writing a lot of extra code to make sure your application works with Internet Explorer and standards-compliant browsers such as Firefox and Opera.
The Microsoft AJAX Library provides an abstraction layer that smoothes over the difference between browsers. You handle a DOM event either with the $addHandler() shortcut or the $addHandlers() shortcut. For example, the page in Listing 33.11 contains an element. In the pageLoad() method, the doSomething method is wired to the Button click event with the help of the $addHandler() shortcut. The doSomething() method simply displays an alert box with the message “Hello World!”. The $addHandler() shortcut accepts three parameters: the DOM element, the name of the event to handle, and a reference to the method to execute. If you need to wire up multiple event handlers to the same element, you can use the $addHandlers() method. The $addHandlers() method accepts a list of event handlers for its second parameter. For example, the page in Listing 33.12 handles a <div> element’s mouseover and mouseout events. The background color of the <div> element changes from white to yellow (see Figure 33.7). FIGURE 33.7 Handling the mouseover and mouseout events. When you are wiring up DOM event handlers, it is important to unwire the event handlers when you are done using them. Unfortunately, Internet Explorer exhibits bad memory leaks when you create references between JavaScript objects and DOM objects. You can avoid these memory leaks by calling either the $removeHandler or $clearHandlers() shortcut method. In Listing 33.11, the $removeHandler() method is used to remove the doSomething() handler from the Button click event. In Listing 33.12, the $clearHandlers() method is used to remove all the handlers from divHover’s <div> element. In both cases, the method is called within an application-disposing event handler.
NOTE The application-disposing event is raised whenever the user moves from the current page. For example, disposing is raised when the user refreshes the browser, navigates to a new page, or closes the browser window.
Retrieving DOM Event Information When you are writing a normal JavaScript application, retrieving information about an event is just as difficult as wiring up a handler for a client-side event. Again, Internet Explorer represents event information in a completely different way than Firefox or Opera. The Microsoft AJAX Library, once again, enables you to smooth over the differences between the different browsers. If you create an event handler with the Microsoft AJAX Library, event information is passed to the event handler as a parameter. In particular, an instance of the Sys.UI.DomEvent class is passed to the event handler. The Sys.UI.DomEvent class has a number of useful properties: . altKey—Returns true when the Alt key is pressed. . button—Returns a value from the Sys.UI.MouseButton enumeration: leftButton, middleButton, or rightButton. . charCode—Returns the code for the key pressed that raised the event. Use the Sys.UI.Key enumeration to compare the charCode against the particular types of keys, such as the Backspace, Tab, and Enter. . clientX—Returns the horizontal position of the mouse relative to the client area of the browser window, excluding the scroll bars. . clientY—Returns the vertical position of the mouse relative to the client area of the browser window, excluding the scroll bars. . ctrlKey—Returns true when the Ctrl key is pressed. . offsetX—Returns the horizontal position of the mouse relative to the element that raised the event. . offsetY—Returns the vertical position of the mouse relative to the element that raised the event. . screenX—Returns the horizontal position of the mouse relative to the entire screen. . screenY—Returns the vertical position of the mouse relative to the entire screen. . shiftKey—Returns true when the Shift key is pressed. . target—Returns the original element that raised the event (as distinct from the element associated with the event handler). . type—Returns the name of the event (for example, click).
The Sys.UI.DomEvent class also has two useful methods: . preventDefault—Stops the default action associated with the event. . stopPropagation—Stops the event from bubbling up to its parent element.
The preventDefault method is especially useful. If you want to prevent the default event associated with performing an action, then you can call this method to cancel it. For example, if you add a