3.1. The XMLHttpRequest ObjectThe foundation of Ajax is the XMLHttpRequest object, which enables you to make HTTP requests (and get responses), without performing a full page postback and refresh.
A significant portion of the web browser market does support XMLHttpRequest and therefore is Ajax-compatible. According to a study conducted by Net Applications (http://www.netapplications.com) in November 2005, approximately 99 percent of the browsers in use are Internet Explorer 5 or later, Mozilla 1.0 or later, Firefox 1.0 or later, Opera 8 or later, Safari 1.2 or later, or KDE 3 or later. So does this mean that almost everybody can experience Ajax applications?
The answer, unfortunately, is no. Depending on which study you trust, between 5 to 15 percent of web users have disabled JavaScript in their browser, perhaps because of recurring reports of security vulnerabilities in browsers or because of corporate policies. As a result, it's possible a significant portion of your users cannot use applications that rely on JavaScript, which includes Ajax applications, in spite of the widespread adoption of up-to-date browsers. Therefore, you always need a fallback plan for those times your application encounters an Ajax-resistant browser. 3.1.1. Programming the XMLHttpRequest ObjectHow you instantiate the XMLHttpRequest object depends on the browser in which your code executes. For Internet Explorer 5 and later versions, the code shown in the following snippet does the work. It tries two methods to instantiate XMLHttpRequest, because different versions of Internet Explorer have different versions of the Microsoft XML library installed on the system. To avoid error messages when one of the methods fails, two TRy-catch blocks are used. var XMLHTTP = null; try { XMLHTTP = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { XMLHTTP = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { } } For browsers other than Internet Explorer, a simpler syntax is available: XMLHTTP = new XMLHttpRequest(); So all that is required is to determine which browser type is in use and then instantiate the XMLHttpRequest object accordingly. For instance, the following code checks whether an ActiveX object can be instantiated by testing the ActiveXObject property of the window object; if this code works, the browser must be Internet Explorer. if (window.ActiveXObject) { // it's probably IE } Similarly, you can use the following snippet to check for the presence of an XMLHttpRequest object, which, if found, means that you are using Mozilla and its derivatives, or that you are using Opera, Konqueror, or Safari: if (XMLHttpRequest) { // it's probably not IE } However, checking for the XMLHttpRequest object directly causes Internet Explorer to display the error message "XMLHttpRequest is undefined," as shown in Figure 3-1. This will change with release of Internet Explorer 7 (currently in beta test), which will provide a native XMLHttpRequest object. Figure 3-1. Internet Explorer does not like our codeWhat's needed instead is an approach that uses all of the tests shown here. The JavaScript typeof operator is used to determine the type of an expression 47nd returns "undefined" as a string if the expression evaluates to "undefined." This feature enables you to detect browsers that are not Internet Explorer, as shown in this code: if (typeof XMLHttpRequest != "undefined") { //it's not IE <= 6 } Here's code for a function, getXMLHTTP(), that aggregates the previous snippets to return an XMLHttpRequest object regardless of which Ajax-enabled, JavaScript-activated browser is used. function getXMLHTTP() { var XMLHTTP = null; try { XMLHTTP = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { XMLHTTP = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { if (typeof XMLHttpRequest != "undefined") { XMLHTTP = new XMLHttpRequest(); } } } return XMLHTTP; } Another approach is to use standard JavaScript to determine browser capabilities and check window.XMLHttpRequest instead of just XMLHttpRequest to find out whether the native XMLHttpRequest object is supported by the browser. Using this technique, the function to return the object can be written slightly differently, as shown in the following code: function getXMLHTTP() { var XMLHTTP = null; if (window.ActiveXObject) { try { XMLHTTP = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { XMLHTTP = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { } } } else if (window.XMLHttpRequest) { try { XMLHTTP = new XMLHttpRequest(); } catch (e) { } } return XMLHTTP; } The XMLHttpRequest object, no matter which browser created it, has a set of properties and methods that are used for sending HTTP requests and receiving the server's response. In most scenarios, the following four steps must be taken to create an HTTP request and evaluate the return values:
Setting the onreadystatechange property of the XMLHttpRequest object provides the callback mechanism for the HTTP response. The property name is related to the name of another XMLHttpRequest property, readyState, which indicates the state of XMLHttpRequest object with five possible values, as listed in Table 3-1.
Whenever the value of readyState changes, the function provided in the onreadystatechange property is called. In this function, you first have to check the value of readyState; typically, you are determining if the value is 4. Then some other properties of the XMLHttpRequest object come into play. The status property contains the HTTP status returned by the request; if everything worked, the status is 200. The statusText property holds the associated textual description of the HTTP status. For instance, for HTTP status 200, the value of statusText is "OK". Checking the status property, however, is more reliable, because different web servers might return different text for the status codes. Two properties provide access to the return value from the server:
The following script is a small example that illustrates how to use the XMLHttpRequest object. In the example, the request is made to an ASP.NET page named ajax.aspx. In the first step, the getXMLHTTP() function is used to create the XMLHttpRequest object. If that works (that is, the return value of the function is not null), a GET request is sent to the server with the parameter sendData=ok (an arbitrary value, just for the example). Then the onreadystatechange property is set to a function, and finally the request is sent to the server. var XMLHTTP = getXMLHTTP(); if (XMLHTTP != null) { XMLHTTP.open("GET", "ajax.aspx?sendData=ok"); XMLHTTP.onreadystatechange = stateChanged; XMLHTTP.send(null); } The stateChanged() function might look something like the following (with error reporting omitted). This script displays whatever text the server has sent as the response. function stateChanged() { if (XMLHTTP.readyState == 4 && XMLHTTP.status == 200) { window.alert(XMLHTTP.responseText); } }
Note that the function that is called when readyState changes does not accept any parameters. Therefore, the XMLHttpRequest object must be global; otherwise, you cannot access it from within the function invoked by the asynchronous call. Of course, you must also have server code to handle the request made by the XMLHttpRequest object. The following code shows a Page_Load event handler in an ASP.NET page that can respond to the asynchronous request made by the XMLHttpRequest object. void Page_Load() { if (Request.QueryString["sendData"] != null && Request.QueryString["sendData"] == "ok") { Response.Write("Hello from the server!"); Response.End(); } } You can put all of these pieces together (both client script and server code) into a single page named ajax.aspx, as shown in Example 3-1.
Example 3-1. A simple example combining Ajax and ASP.NET.
As you can see in Figures 3-2, 3-3, and 3-4, this code works beautifully in Internet Explorer, Firefox, and Konqueror (using Mono for ASP.NET), the most commonly used browsers. It should work equally well in any other browser you choose to test. Figure 3-2. The example works in Internet ExplorerFigure 3-3. The example works in FirefoxFigure 3-4. The example works in Konqueror and other browsers
If you want to use a POST command for the HTTP request, just set the first parameter of the open() method appropriately. Using PO0ST is especially important when you are sending 500 bytes or more of data (you might exceed the maximum URL length for the server) or when you want to avoid 53aching by proxy servers. The data you want to send is provided in the send() function, in name-value pairs and URL-encoded, if needed, as shown in the following snippet: XMLHTTP.open("POST", "ajax.aspx"); XMLHTTP.onreadystatechange = stateChanged; XMLHTTP.send("sendData=ok&returnValue=123"); Data sent with a POST command can be read on the server, in the case of ASP.NET using Request.Form for POST instead of the Request.QueryString property used to read GET requests. For web service calls that use the SOAP protocol, you may have to send XML directly, without URL-encoding. However, for this to work with the Safari and Konqueror browsers (and therefore to maximize your potential audience), you have to explicitly set the request content type to text/xml. (Other browsers do not require this content specification.) The following snippet shows how to do this: XMLHTTP.open("POST", "ajax.aspx"); XMLHTTP.onreadystatechange = stateChanged; XMLHTTP.setRequestHeader("Content-Type", "text/xml"); XMLHTTP.send("<soap:Envelope>...</soap:Envelope>");
A word regarding security: by default, XMLHttpRequest can access resources only in the same domain as the client script. Unfortunately, this limits the capabilities of the technology, since there is no easy way to call a web service using Ajax. (However, Chapter 10 shows some ways to get around this limitation.) Mozilla browsers support accessing remote servers in another domain by explicitly prompting the user for additional privileges; Figure 3-5 shows the message prompting the user for those. However, this approach generates several additional issues of its own and is not browser-agnostic, which is why this is very rarely in use nowadays and not used in this book. So all HTTP requests illustrated in this book are to the server from which the page itself originates. Figure 3-5. Requesting additional privileges in Mozilla browsers |