XMLHttpRequestHello, server!
This is a JavaScript DOM exercise from Chapter 2 of Microsoft AJAX Library Essentials: JavaScript in ASP.NET AJAX 1.0 Explained.
XMLHttpRequest is the object that enables the JavaScript code to make asynchronous
HTTP server requests. This allows you to initiate HTTP requests and receive responses
from the server in the background, without requiring the user to submit the page
to the server. This feature, combined with the possibility to manipulate the web
page using DOM and CSS, allows you to implement responsive functionality and visual
effects backed with live data from the server, without the user experiencing any
visual interruptions. This is AJAX.
The XMLHttpRequest object was initially implemented
by Microsoft in 1999 as an ActiveX object in Internet Explorer, and eventually became
de facto standard for all the browsers, being supported as a native object by all
modern web browsers except Internet Explorer 6.
Note that even if XMLHttpRequest has become a de facto standard in the web browsers, it is not yet a W3C standard.
Similar functionality is proposed by the W3C DOM Level 3 Load and Save specification
standard, which hasn't been implemented yet by web browsers.
The typical sequence
of operations when working with XMLHttpRequest is as follows:
XMLHttpRequest object.
XMLHttpRequest object to make an asynchronous call to a server page, defining a callback function that will be executed automatically when the server response is received.
XMLHttpRequest ObjectThe XMLHttpRequest is implemented in different ways by the browsers. In Internet
Explorer 6 and older, XMLHttpRequest is implemented as an ActiveX control, and you
instantiate it like this:
xmlhttp = new ActiveXObject("Microsoft.XMLHttp");
For the other web browsers – including Firefox, Opera and Safari, and Internet Explorer
7, XMLHttpRequest is a native object, so you create instances of it like this:
xmlhttp = new XMLHttpRequest();
The ActiveX XMLHttp library comes is many more flavors and versions that you could imagine. Each piece of Microsoft software, including Internet Explorer and MDAC, came with new versions of this ActiveX control, each having its own name. Microsoft.XMLHttp is the oldest and is supported on all Windows machines, but the newer versions have performance improvements.
It is possible to write JavaScript code that automatically detects the latest XMLHttp version installed on the visitor’s machine, if he or she is using Internet Explorer 6 or older. The technique is described in AJAX and PHP: Building Responsive Web Applications (Packt, 2006), but we will not insist on it here because the feature is included in the Microsoft AJAX Library.
The following JavaScript function creates an XMLHttpRequest
instance by using the native object if available, or the Microsoft.XMLHttp ActiveX
control for visitors that use Internet Explorer 6 or older:
// creates anXMLHttpRequestinstance function createXMLHttpRequestObject() { // xmlHttp will store the reference to theXMLHttpRequestobject var xmlHttp; // try to instantiate the nativeXMLHttpRequestobject try { // create anXMLHttpRequestobject xmlHttp = newXMLHttpRequest(); } catch(e) { // assume IE6 or older try { xmlHttp = new ActiveXObject("Microsoft.XMLHttp"); } catch(e) { } } // return the created object or display an error message if (!xmlHttp) alert("Error creating theXMLHttpRequestobject."); else return xmlHttp; }
This
function uses the JavaScript try/catch construct, which is a powerful exception-handling
technique that was initially implemented in OOP (Object Oriented Programming) languages.
Basically, when an error happens at run time in the JavaScript code, an exception
is thrown. The exception is an object that contains the details of the error. Using
the try/catch syntax, you can catch the exception and handle it locally, so that
the error won't be propagated to the user's browser.
The try/catch syntax is as follows:
try
{
// code that might generate an exception
}
catch (e)
{
// code that executes if an exception was thrown in the try block
// (exception details are available through the e parameter)
}
You place any code that might generate errors inside
the try block. If an error happens, the execution is passed immediately to the catch
block. If no error happens inside the try block, then the code in the catch block
never executes.
Run-time exceptions propagate from the point they were raised, up
through the call stack of your program. The call stack is the list of methods that
are being executed. So if a function A() calls a function B() which at its turn
calls a function called C(), then the call stack will be formed of these three methods.
If an exception happens in C(), you can handle it using a try/catch block right
there. If the exception isn't caught and handled in C(), it propagates, to B(),
and so on. The final layer is the web browser. If your code generates an exception
that you don't handle, the exception will end up getting caught by the web browser,
which may display an unpleasant error message to your visitor.
The way you handle each exception depends very much on the situation at hand. Sometimes you will simply ignore the error, other times you will flag it somehow in the code, or you will display an error message to your visitor. In this book you will meet all kinds of scenarios.
In our particular case, when we want to create an XMLHttpRequest object,
we will first try to create the object as if it was a native browser object, like
this:
// try to instantiate the nativeXMLHttpRequestobject try { // create anXMLHttpRequestobject xmlHttp = newXMLHttpRequest(); }
Internet Explorer 7, Mozilla, Opera, Safari, and other browsers will execute this piece of code just fine,
and no error will be generated, because XMLHttpRequest is a supported natively. However,
Internet Explorer 6 and its older versions won't recognize the XMLHttpRequest object, an exception
will be generated, and the execution will be passed to the catch block. For Internet Explorer 6 and older
versions, the XMLHttpRequest object needs to be created as an ActiveX control:
catch(e)
{
// assume IE6 or older
try
{
xmlHttp = new ActiveXObject("Microsoft.XMLHttp");
}
catch(e) { }
}
Every JavaScript programmer
seems to have his or her own technique for creating the XMLHttpRequest object, and
surprisingly enough, all techniques work just fine. The implementation we presented
uses try and catch to instantiate the object, because it (reasonably) guarantees
the best chance of working well with future browsers, while doing proper error checking
without consuming too many lines of code.
Alternatively, you could, for example,
check whether your browser supports XMLHttpRequest before trying to instantiate
it, using the typeof function:
if (typeofXMLHttpRequest!= "undefined") { xmlHttp = newXMLHttpRequest(); }
Using typeof can often prove to be very helpful. In our
particular case, using typeof doesn't eliminate the need to guard against errors
using try/catch, so you would just end up typing more lines of code.
Another technique is to use a JavaScript feature called object detection. This feature allows you to check whether a particular object is supported by the browser, and works like this:
if (window.XMLHttpRequest) { xmlHttp = newXMLHttpRequest(); }
If window.ActiveX exists, then you know that the browser is Internet Explorer. Once again, we're not using this technique because it would simply add more lines of code without bringing any benefits; but the ideas are good to keep nevertheless.
If you decide to use
object detection, please be sure to check for XMLHttpRequest first before checking
for ActiveX support. The reason for this recommendation is Internet Explorer 7,
which supports both the ActiveX XMLHttp control, and the native XMLHttpRequest object
(this one being the better choice).
At the end of our createXMLHttpRequestObject
function, we test that after all the efforts, we have ended up obtaining a valid
XMLHttpRequest instance:
// return the created object or display an error message
if (!xmlHttp)
alert("Error creating the XMLHttpRequest object.");
else
return xmlHttp;
Here we used the reverse effect of JavaScript’s object detection feature, which in our opinion is even nicer than the feature itself. Object detection says that JavaScript will evaluate a valid object instance, such as xmlHttp, to true. The negation of this expression, (!xmlHttp), returns true not only if xmlHttp is false, but also if it is null or undefined.
After creating the XMLHttpRequest object you can do lots of interesting things with it. Although it has different ways of being instantiated, all the instances of XMLHttpRequest are supposed to share the same API (Application Programming Interface) and support the same functionality. This API is formed of the following methods and properties:
|
Method/Property |
Description |
|
abort |
Stops the current request. |
|
getAllResponseHeaders() |
Returns the response headers as a string. |
|
getResponseHeader("headerLabel") |
Returns a single response header as a string. |
|
open("method", "URL"[, asyncFlag[, "userName"[, "password"]]]) |
Initializes the request parameters.
|
|
send(content) |
Performs the HTTP request. |
|
setRequestHeader ("label", "value") |
Sets a HTTP request header. |
|
onreadystatechange |
Used to set the callback function that handles request state changes. |
|
readyState |
Returns
the status of the request:
0 = uninitialized |
|
responseText |
Returns
the server response as a string. |
|
responseXML |
Returns the server response as an XML document that can be manipulated using JavaScript's DOM functions. |
|
status |
Returns the status code of the request. |
|
statusText |
Returns
the status message of the request. |
The methods you will use with every server request are open() and send(). The open() method configures a request by setting various parameters, and send() sends the request to the server. When the request is made asynchronously, before calling send you will also need to set the onreadystatechange property with the callback method to be executed when the status of the request changes, thus enabling the AJAX mechanism.
The open() method is used for initializing a request. It has two required parameters and a few optional ones. The open() method doesn't initiate a connection to the server; it is only used to set the connection options. The first parameter specifies the method used to send data to the server page, and it can have a value such as GET, POST, or PUT. The second parameter is URL, which specifies where you want to send the request. The URL can be complete or relative. If the URL doesn't specify a resource accessible via HTTP, the first parameter is ignored.
The third parameter of open(), called async, specifies whether the request should be handled asynchronously; true means that your code processing carries on after the send() method returns without waiting for a response from the server; false means that the script waits for a response before continuing processing, freezing the web page functionality. To enable asynchronous processing (which is the heart of the AJAX mechanism), you will need to set async to true, and handle the onreadystatechange event to process the response from the server.
When using GET to pass parameters, you send the parameters using the URL's query string, as in http://localhost/ajax/test.aspx?param1=x¶m2=y. This server request passes two parameters to the server—a parameter called param1 with the value x, and a parameter called param2 with the value y:
// call the server to execute the server side operation
xmlHttp.open("GET", "http://localhost/ajax/test.aspx?param1=x¶m2=y", true);
xmlHttp.onreadystatechange = handleRequestStateChange;
xmlHttp.send(null);
When using POST, you send the query string as a parameter of the send() method, instead of joining it on to the base URL, like this:
// call the server page to execute the server side operation
xmlHttp.open("POST", "http://localhost/ajax/test.aspx", true);
xmlHttp.onreadystatechange = handleRequestStateChange;
xmlHttp.send("param1=x¶m2=y");
The to code samples should have the same effects. In practice, there are a few differences between POST and GET that you should know about:
GET can help with debugging because you can simulate GET requests with a web browser, so you can easily see with your own eyes what your server script generates.
POST method is required when sending data larger than 512 bytes, which cannot be handled by GET.
GET is meant to be used for retrieving data from the server, while POST is meant to submit changes. In the real world, it's good to obey by these rules, otherwise strange things can happen. For example, search engines send GET requests to read data from the web, but they never POST any data. If you use GET to submit changes, and a search engine becomes aware of the address of the server script, that search engine could start modifying your data—and you certainly don't want that!
The minimal implementation of a function named process() that makes asynchronous server calls using GET looks like this:
function process()
{
// call the server to execute the server side operation
xmlHttp.open("GET", "server_script.php", true);
xmlHttp.onreadystatechange = handleRequestStateChange;
xmlHttp.send(null);
}
This method has the following potential problems:
process() may be executed even if xmlHttp doesn't contain a valid XMLHttpRequest instance. This may happen if, for example, the user's browser doesn't support XMLHttpRequest. This would cause an unhandled exception to happen. Our other efforts to handle errors don't help very much if we aren't consistent and do something about the process function as well.
process() isn't protected against other kinds of errors that could happen. For example, as you will see later in this chapter, some browsers will generate a security exception if they don't like the server you want to access with the XMLHttpRequest object (more on security in Chapter 3).
A better version of process() looks like that:
// performs a server request and assigns a callback function
function process()
{
// continue only if xmlHttp isn't void
if (xmlHttp)
{
// try to connect to the server
try
{
// initiate server request
xmlHttp.open("GET", "server_script.php", true);
xmlHttp.onreadystatechange = handleRequestStateChange;
xmlHttp.send(null);
}
// display an error in case of failure
catch (e)
{
alert("Can't connect to server:\n" + e.toString());
}
}
}
If xmlHttp is null (or false) we don't display yet another error message, because we assume such a message was already displayed by the createXMLHttpRequestObject function. We make sure to signal any other connection problems though.
XMLHttpRequest Server ResponseWhen making an asynchronous request (such as in the code snippets presented earlier), the execution of xmlHttp.send() doesn't freeze until the server response is received; instead, the execution continues normally. In the process() function shown earlier, the handleRequestStateChange() function is defined as the callback method that should handle request state changes.
Usually, handleRequestStateChange() is called four times, for each time the request enters a new stage. The readyState property can have one the following values representing the possible stages of the request:
0 = uninitialized
1 = loading
2 = loaded
3 = interactive
4 = complete
Except state 3, all the other states have pretty self-explaining names. The interactive state is an intermediate state when the response has been partially received. In our AJAX applications we will only use the complete state, which marks that a response has been fully received from the server.
The typical implementation of handleRequestStateChange is shown in the following code snippet, which highlights the portion where you actually get to read the response from the server.
// function executed when the state of the request changes
function handleRequestStateChange()
{
// continue if the process is completed
if (xmlHttp.readyState == 4)
{
// continue only if HTTP status is "OK"
if (xmlHttp.status == 200)
{
// retrieve the response
response = xmlHttp.responseText;
// do something with the response
// ...
// ...
}
}
}
Before attempting to read the received data, we also verify that the response status code is 200. Sending such a code indicating the status of the request is part of the HTTP protocol, and 200 is the status code that specifies that the request completed successfully. Other popular HTTP status codes are 404, which indicate that the requested resource couldn’t be found, and 500, which indicates a server error.
Once again we can use try/catch blocks to handle errors that could happen while initiating a connection to the server, or while reading the response from the server. A safer version of the handleRequestStateChange() function looks like this:
// function executed when the state of the request changes
function handleRequestStateChange()
{
// continue if the process is completed
if (xmlHttp.readyState == 4)
{
// continue only if HTTP status is "OK"
if (xmlHttp.status == 200)
{
try
{
// retrieve the response
response = xmlHttp.responseText;
// do something with the response
// ...
// ...
}
catch(e)
{
// display error message
alert("Error reading the response: " + e.toString());
}
}
else
{
// display status message
alert("There was a problem retrieving the data:\n" +
xmlHttp.statusText);
}
}
}
Implement the exercise step by step and find detailed explanations in our book, Microsoft AJAX Library Essentials: JavaScript in ASP.NET AJAX 1.0 Explained.