Previous Page
Next Page

Hack 2. Use the Request Object to POST Data to the Server

Step beyond the traditional mechanism of posting your user's form values.

This hack uses the POST HTTP request method to send data, communicating with the server without disrupting the user's interaction with the application. It then displays the server's response to the user. The difference between this hack's approach to posting data and the typical form-submission method is that with Ajax, the page is not altered or refreshed when the application connects with the server to POST it the data. Thus, the user can continue to interact with the application without waiting for the interface to be rebuilt in the browser.

Imagine that you have a web portal in which several regions of the page or view provide the user with a variety of services. If one of these regions involves posting data, the entire application might have a more responsive feel if the POST request happens in the background. This way, the entire page (or segments of it) does not have to be refreshed in the browser.

The example web page used in this hack is a simple one. It requests users to enter their first and last names, gender, and country of origin, and then click a button to POST the data. Figure 1-1 shows what the web page looks like in a browser window.

Figure 1-1. Please Mister POST man


Here's the code for the HTML page:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
        "http://www.w3.org/TR/1999/REC-html401-19991224/strict.dtd">
<html>
<head>
    <script type="text/javascript" src="/parkerriver/js/hack2.js"></script>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>Send a data tidbit</title>
</head>
<body>
<h3>A Few Facts About Yourself...</h3>
<form action="javascript:void%200" onsubmit="sendData(  );return false">
    <p>First name: <input type="text" name="firstname" size="20"> </p>
    <p>Last name: <input type="text" name="lastname" size="20"> </p>
    <p>Gender: <input type="text" name="gender" size="2"> </p>
    <p>Country of origin: <input type="text" name="country" size="20"> </p>
    <p><button type="submit">Send Data</button></p>
</form>
</body>
</html>

You may be wondering about the weird-looking form action="javascript:void%200" part. Because we are calling JavaScript functions when the form is submitted, we do not want to give the action attribute anything but a JavaScript URL that has no return value, such as "javascript:void 0". We have to encode the space between void and 0, which is where the %20 comes in. If JavaScript is disabled in the user's browser, clicking the submit button on the form has no effect because the action attribute does not point to a valid URL. In addition, certain HTML validators will display warnings if you use action="". Another way of writing this code is to include the function calls as part of the window.onload event handler in the JavaScript .js file, which is the approach used by most hacks in this book.


The first code element of interest is the script tag, which imports the JavaScript code (in a file named hack2.js). The form tag's onsubmit attribute specifies a function called sendData( ), which in turn formats the data for a POST request (by calling another function, setQueryString( )) and sends the data to the server. For brevity's sake, we've saved the description of checking for blank fields for a later hack ("Validate a Text Field or textarea for Blank Fields" [Hack #22]), but web applications should take this step before they hit the server.

The hack2.js file defines the necessary JavaScript. Here is the setQueryString( ) function:

function setQueryString(  ){
    queryString="";
    var frm = document.forms[0];
    var numberElements =  frm.elements.length;
    for(var i = 0; i < numberElements; i++) {
        if(i < numberElements-1) {
            queryString += frm.elements[i].name+"="+
                           encodeURIComponent(frm.elements[i].value)+"&";
        } else {
            queryString += frm.elements[i].name+"="+
                           encodeURIComponent(frm.elements[i].value);
        }

    }
}

This function formats a POST-style string out of all the form's input elements. All the name/value pairs are separated by an & character, except for the pair representing the last input element in the form. The entire string might look like:

firstname=Bruce&lastname=Perry&gender=M&country=USA

Now you have a string you can use in a POST HTTP request. Let's look at the JavaScript code that sends the request. Everything starts with the sendData( ) function. The code calls this function in the HTML form tag's onsubmit attribute:

var request;
var queryString;   //will hold the POSTed data
function sendData(  ){
    setQueryString(  );
    var url="http://www.parkerriver.com/s/sender";
    httpRequest("POST",url,true);
}

/* Initialize a request object that is already constructed.
 Parameters:
   reqType: The HTTP request type, such as GET or POST.
   url: The URL of the server program.
   isAsynch: Whether to send the request asynchronously or not. */
function initReq(reqType,url,isAsynch){
    /* Specify the function that will handle the HTTP response */
    request.onreadystatechange=handleResponse;
    request.open(reqType,url,isAsynch);
    /* Set the Content-Type header for a POST request */
    request.setRequestHeader("Content-Type",
            "application/x-www-form-urlencoded; charset=UTF-8");
    request.send(queryString);
}

/* Wrapper function for constructing a request object.
 Parameters:
  reqType: The HTTP request type, such as GET or POST.
  url: The URL of the server program.
  asynch: Whether to send the request asynchronously or not. */
  
function httpRequest(reqType,url,asynch){
    //Mozilla-based browsers
    if(window.XMLHttpRequest){
        request = new XMLHttpRequest(  );
    } else if (window.ActiveXObject){
        request=new ActiveXObject("Msxml2.XMLHTTP");
        if (! request){
            request=new ActiveXObject("Microsoft.XMLHTTP");
        }
    }
    //the request could still be null if neither ActiveXObject
    //initialization succeeded
    if(request){
        initReq(reqType,url,asynch);
    } else {
        alert("Your browser does not permit the use of all "+
              "of this application's features!");
    }
}

The purpose of the httpRequest( ) function is to check which request object the user's browser is associated with (see "Detect Browser Compatibility with the Request Object" [Hack #1]). Next, the code calls initReq( ), whose parameters are described in the comment just above the function definition.

The code request.onreadystatechange=handleResponse; specifies the event-handler function that deals with the response. We'll look at this function a little later. The code then calls the request object's open( ) method, which prepares the object to send the request.

Setting Headers

The code can set any request headers after calling open( ). In our case, we have to create a Content-Type header for a POST request.

Firefox required the additional Content-Type header; Safari 1.3 did not. (We were using Firefox 1.02 at the time of writing this hack.) It is a good idea to add the proper header because in most cases the server is expecting it from a POST request.


Here's the code for adding the header and sending the POST request:

request.setRequestHeader("Content-Type",
        "application/x-www-form-urlencoded; charset=UTF-8");
request.send(queryString);

If you enter the raw queryString value as a parameter, the method call looks like this:

send("firstname=Bruce&lastname=Perry&gender=M&country=USA");

Ogling the Result

Once your application POSTs data, you want to display the result to your users. This is the responsibility of the handleResponse( ) function. Remember the code in the initReq( ) function:

 request.onreadystatechange=handleResponse;

When the request object's readyState property has a value of 4, signifying that the object's operations are complete, the code checks the HTTP response status for the value 200. This value indicates that the HTTP request has succeeded. The responseText is then displayed in an alert window. This is somewhat anticlimactic, but I thought I'd keep this hack's response handling simple, because so many other hacks do something more complex with it!

Here is the relevant code:

//event handler for XMLHttpRequest
function handleResponse(  ){
    if(request.readyState == 4){
        if(request.status == 200){
            alert(request.responseText);
        } else {
            alert("A problem occurred with communicating between "+
                  "the XMLHttpRequest object and the server program.");
        }
    }//end outer if
}

Figure 1-2 shows what the alert window looks like after the response is received.

Figure 1-2. Alert! Server calling...


The server component returns an XML version of the POSTed data. Each parameter name becomes an element name, with the parameter value as the element content. This POSTed data is nested within params tags. The component is a Java servlet. The servlet is not the main focus of this hack, but here's some code anyway, for the benefit of readers who are curious about what is happening on the server end:

protected void doPost(HttpServletRequest httpServletRequest, 
                      HttpServletResponse httpServletResponse) throws 
                      ServletException, IOException {
    Map reqMap = httpServletRequest.getParameterMap(  );
    String val=null;
    String tag = null;
    StringBuffer body = new StringBuffer("<params>\\n");
    boolean wellFormed = true;
    Map.Entry me = null;
    for(Iterator iter= reqMap.entrySet().iterator(  );iter.hasNext(  );) {
        me=(Map.Entry) iter.next(  );
        val= ((String[])me.getValue(  ))[0];
        tag = (String) me.getKey(  );
        if (! XMLUtils.isWellFormedXMLName(tag)){
            wellFormed=false; break;
        }
        body.append("<").append(tag).append(">").
        append(XMLUtils.escapeBodyValue(val)).
append("</").append(tag).append(">\\n"); } if(wellFormed) { body.append("</params>"); sendXML(httpServletResponse,body.toString( )); } else { sendXML(httpServletResponse,"<notWellFormedParams />"); } }

The code uses XMLUtils, a Java class from the Jakarta Commons Betwixt open source package, to check whether the parameter names are well formed, as well as whether the parameter values contain invalid XML content and thus have to be escaped. If for some reason the component is POSTed data that contains nonwell-formed parameter names (such as na< >me instead of name), the servlet returns an empty XML element reporting this condition.


Previous Page
Next Page