Level: Intermediate

Philip McCarthy (philmccarthy@gmail.com), Software Development Consultant, Independent Consultant

20 Sep 2005

Listing 1. Relevant fragments of the shopping cart example

    <!-- Table of products from store's catalog, one row per item -->
    <th>Name</th> <th>Description</th> <th>Price</th> <th></th>
    ...
    <tr>
      <!-- Item details -->
      <td>Hat</td> <td>Stylish bowler hat</td> <td>$19.99</td>
      <td>
        <!-- Click button to add item to cart via Ajax request -->
        <button onclick="addToCart('hat001')">Add to Cart</button>
      </td>
    </tr>
    ...

    <!-- Representation of shopping cart, updated asynchronously -->
    <ul id="cart-contents">

      <!-- List-items will be added here for each item in the cart -->

    </ul>

    <!-- Total cost of items in cart displayed inside span element -->
    Total cost: <span id="total">$0.00</span>

| Back to top

Dispatching an XMLHttpRequest

Listing 2. Creating a cross-browser XMLHttpRequest

    /*
     * Returns a new XMLHttpRequest object, or false if this browser
     * doesn't support it
     */
    function newXMLHttpRequest() {

      var xmlreq = false;

      if (window.XMLHttpRequest) {

        // Create XMLHttpRequest object in non-Microsoft browsers
        xmlreq = new XMLHttpRequest();

      } else if (window.ActiveXObject) {

        // Create XMLHttpRequest via MS ActiveX
        try {
          // Try to create XMLHttpRequest in later versions
          // of Internet Explorer

          xmlreq = new ActiveXObject("Msxml2.XMLHTTP");

        } catch (e1) {

          // Failed to create required ActiveXObject

          try {
            // Try version supported by older versions
            // of Internet Explorer

            xmlreq = new ActiveXObject("Microsoft.XMLHTTP");

          } catch (e2) {

            // Unable to create an XMLHttpRequest with ActiveX
          }
        }
      }

      return xmlreq;
    }

Listing 3 puts these steps together.

Listing 3. Dispatching an Add to Cart XMLHttpRequest

    /*
     * Adds an item, identified by its product code,
     * to the shopping cart
     * itemCode - product code of the item to add.
     */
    function addToCart(itemCode) {

      // Obtain an XMLHttpRequest instance
      var req = newXMLHttpRequest();

      // Set the handler function to receive callback notifications
      // from the request object
      var handlerFunction = getReadyStateHandler(req, updateCart);
      req.onreadystatechange = handlerFunction;

      // Open an HTTP POST connection to the shopping cart servlet.
      // Third parameter specifies request is asynchronous.
      req.open("POST", "cart.do", true);

      // Specify that the body of the request contains form data
      req.setRequestHeader("Content-Type",
                           "application/x-www-form-urlencoded");

      // Send form encoded data stating that I want to add the
      // specified item to the cart.
      req.send("action=add&item="+itemCode);
    }

| Back to top

Servlet request handling

Listing 4. Servlet code to handle Ajax requests

    public void doPost(HttpServletRequest req,
      HttpServletResponse res)
          throws java.io.IOException {

      Cart cart = getCartFromSession(req);

      String action = req.getParameter("action");
      String item = req.getParameter("item");

      if ((action != null)&&(item != null)) {

        // Add or remove items from the Cart
        if ("add".equals(action)) {
          cart.addItem(item);

        } else if ("remove".equals(action)) {
          cart.removeItems(item);

        }
      }

      // Serialize the Cart's state to XML
      String cartXml = cart.toXml();

      // Write XML to response.
      res.setContentType("application/xml");
      res.getWriter().write(cartXml);
    }

Listing 5 shows an example of the XML produced by the `Cart.toXml()` method. It's pretty straightforward. Note the `generated` attribute on the `cart` element, which is a timestamp produced by `System.currentTimeMillis()`.

Listing 5. Example XML serialization of the Cart object

    <?xml version="1.0"?>
    <cart generated="1123969988414" total="$171.95">
      <item code="hat001">
        <name>Hat</name>
        <quantity>2</quantity>
      </item>
      <item code="cha001">
        <name>Chair</name>
        <quantity>1</quantity>
      </item>
      <item code="dog001">
        <name>Dog</name>
        <quantity>1</quantity>
      </item>
    </cart>

| Back to top

Response handling with JavaScript

Listing 6. The getReadyStateHandler() function

    /*
     * Returns a function that waits for the specified XMLHttpRequest
     * to complete, then passes its XML response
     * to the given handler function.
     * req - The XMLHttpRequest whose state is changing
     * responseXmlHandler - Function to pass the XML response to
     */
    function getReadyStateHandler(req, responseXmlHandler) {

      // Return an anonymous function that listens to the
      // XMLHttpRequest instance
      return function () {

        // If the request's status is "complete"
        if (req.readyState == 4) {

          // Check that a successful server response was received
          if (req.status == 200) {

            // Pass the XML payload of the response to the
            // handler function
            responseXmlHandler(req.responseXML);

          } else {

            // An HTTP problem has occurred
            alert("HTTP error: "+req.status);
          }
        }
      }
    }

Listing 7. Updating the page to reflect the cart XML document

    function updateCart(cartXML) {

     // Get the root "cart" element from the document
     var cart = cartXML.getElementsByTagName("cart")[0];

     // Check that a more recent cart document hasn't been processed
     // already
     var generated = cart.getAttribute("generated");
     if (generated > lastCartUpdate) {
       lastCartUpdate = generated;

       // Clear the HTML list used to display the cart contents
       var contents = document.getElementById("cart-contents");
       contents.innerHTML = "";

       // Loop over the items in the cart
       var items = cart.getElementsByTagName("item");
       for (var I = 0 ; I < items.length ; I++) {

         var item = items[I];

         // Extract the text nodes from the name and quantity elements
         var name = item.getElementsByTagName("name")[0]
                                                   .firstChild.nodeValue;

         var quantity = item.getElementsByTagName("quantity")[0]
                                                   .firstChild.nodeValue;

         // Create and add a list item HTML element for this cart item
         var li = document.createElement("li");
         li.appendChild(document.createTextNode(name+" x "+quantity));
         contents.appendChild(li);
       }
     }

     // Update the cart's total using the value from the cart document
     document.getElementById("total").innerHTML =
                                              cart.getAttribute("total");
    }