Adding A Shopping Cart Using Ajax - Part 1

The next logical step in our book store application is to allow users to populate a shopping cart full of books to purchase. After all, the reason we have a book store - or any kind of store - on the web is to make money, right? A shopping cart is, by now, a very familiar metaphor to online shoppers. The shopping cart is nothing more than a container that is used by the customer that holds information about their desired purchases. Let's see if we can create a rudimentary shopping cart using Ajax.

1. Create a new Web Application in Netbeans. Name it AjaxBooks2 and click next. You won't need any frameworks, click Finish. After Netbeans creates the application for you, you can close the index.jsp file and delete it. We'll create all of the files necessary for the application on our own.

2. Right-clck the Web Pages node and choose New > HTML... Name the file index and click finish.

3. Add the following code to the <body> of the document:

<div id="cart">
        Shopping Cart:
    </div>
    <div id="total">$0.00</div>
      <h2>Ajax Books</h2>
  <form>
    <select name="category" id="category" onChange="sendReq(catReq,'get',getCategory(this.value))">
        <option value="all">Show All Books</option>
        <option value="database">Database</option>
        <option value="web">Web</option>
        <option value="JavaScript">JavaScript</option>
        <option value="graphics">Graphics</option>
        <option value="programming">Programming</option>
        <option value="HTML">HTML</option>
    </select>
        
    </form>
    <div id="filter">
          Category: all
      </div>
    <div id="books">
        
    </div>

The above code shouldn't present any problems for you. I've created <div> elements to hold the Shopping Cart and Totals. The rest of the code is nearly identical to the last lesson. The only change is in the onChange event of the drop down box. The parameters of the sendReq() function are a little different. We'll get to that in a moment.

4. Add a <link> element pointing to a CSS file named showBooks.css in the <head> of the document:

<link type="text/css" rel="stylesheet" href="showBooks.css" />

5. Right-click the Web Pages node and choose New > Other. Select the Web node and choose Cascading Style Sheet. Name the file showBooks and click finish.

6. Add the following code to showBooks.css:


body{
    font-family: sans-serif;
}
#filter{
    font: 14pt sans-serif;
    color: green;
}
table{
    border: solid gray 1px;
}
th{
    background-color: green;
    color: white;
}
td{
    border-top: solid gray 1px;
}
.evenRow{
    background-color: rgb(255,255,200);
}
#cart, #total{
    font-size: 9pt;
    background-color: rgb(255,255,200);
    color: navy;
    width: 40%;
    padding: 3px;
    border: dashed gray 1px;
    margin-left: 50%;
    display: none;
}


7. Add a <script> element to the <head> of index.html that references xhr.js.

<script type="text/javascript" src="xhr.js"></script>

8. Right-click the Web Pages node and choose New > Other. Highlight the Web node and Choose JavaScript File. Name the file xhr and click finish.

9. Add the following code to xhr.js:

function xhr(){
    
    try
      {
      // Firefox, Opera 8.0+, Safari
      req=new XMLHttpRequest();
      }
    catch (e)
      {
      // Internet Explorer
      try
        {
        req=new ActiveXObject("Msxml2.XMLHTTP");
        }
      catch (e)
        {
        req=new ActiveXObject("Microsoft.XMLHTTP");
        }
      }
    return req;

}
function sendReq(req,method,url,content){
    if(req.readyState == 4 || req.readyState == 0){
        req.open(method,url,true);
        req.send(content);
    }
}

There are 2 things that are different about this version of xhr.js. First, I've removed the global variable named req. Second, I've included a parameter that expects an XMLHttpRequest object in sendReq(). Both of these things help me to allow for multiple XMLHttpRequest objects within the script. It is my intent to create two XMLHttpRequest - one to fetch the book data for each category and one to fetch individual books to store in the shopping cart.

10. Add a <script> element to the <head> of index.html and add the following code:

		 var catReq = new xhr();
catReq.onreadystatechange=processCatalog;
function processCatalog(){
if(catReq.readyState == 4){
if(catReq.status == 200){
var display = '<table cellpadding="4" cellspacing="0">';
display += '<tr><th>&nbsp;</th><th>Title</th><th>Description</th><th>Price</th><th colspan="2">Author</th><th>&nbsp;</th></tr>';
var records = catReq.responseXML.documentElement.getElementsByTagName('record');
var len = records.length;
for(var i=0; i<len; i++){
if(i%2 == 0)
display += '<tr>';
else
display += '<tr class="evenRow">';
var data = records[i].childNodes;
var dLen = data.length;
for(var j=0; j<dLen; j++){
if(data[j].nodeName == '#text')
continue;
if(data[j].nodeName == 'Category')
continue;
if(data[j].nodeName == 'Coverpic')
display += '<td valign="top"><img src="' + data[j].firstChild.nodeValue + '"></td>';
else if(data[j].nodeName == 'Price')
display += '<td valign="top">$' + data[j].firstChild.nodeValue + '</td>';
else if(data[j].nodeName == 'ISBN'){
display += '<td valign="top">';
display += '<input type="button" value="Add To Cart" onClick="addToCart(';
display += "'" + data[j].firstChild.nodeValue + "'";
display += ')" /></td>';
}
else
display += '<td valign="top">' + data[j].firstChild.nodeValue + '</td>';
}
display += '</tr>';
}
display += '</table>';
document.getElementById('books').innerHTML = display;
document.getElementById('filter').innerHTML = 'Category: ' + document.getElementById('category').value;
}
}
}

function getCategory(val){
return 'filterBooks.jsp?category=' + val;
}

The script starts by creating the XMLHttprequest object named catReq (for catalog request). Then, the processCatalog() function is assigned as the event handler for the readyState property. The processCatalog() function builds a display string by grabbing the responseXML from the server and populating an HTML table. The interesting parts are in the conditionals inside the inner loop. Using an if ... else if ... else mechanism allows me to create different rules for each piece of data. So, since I'm already displaying the category, I don't need it as a column in the display. The Coverpic node is converted into an <img> element that actually displays the book's cover. The ISBN field is used to build a button whose onClick event fires the addToCart() function and passes along the ISBN as a parameter. The rest of the data is simply displayed as table data.

The getCategory() function remains unchanged. It simply appends a value to the end of the query string to be sent to filterBooks.jsp.

You can download a zip file containing all of the images here: BookImages.zip. You'll need to unzip the file and copy the images to an images folder within the web folder inside your web app. I used the filesystem - not Netbeans - to do this.

11. You'll need to copy and paste allBooks.xml from the previous project into the Web Pages node of your web application. Alternatively, Right click allBooks.xml and choose Save Target As.. and save the file to the web folder in the filesystem.

12. Right-click the Web Pages node and choose New > JSP... Name the file filterBooks and click Finish. Replace the the code with the following:

<%@page contentType="text/xml"%>
<%@page pageEncoding="UTF-8"%>
<%@page import="javax.xml.parsers.*,org.w3c.dom.*,java.io.*"  %>


<%
//Grab category
String val = request.getParameter("category");
//Get reference to xml file
File booksFile = new File(application.getRealPath("/allBooks.xml"));
//Create DocumentBuilderFactory
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
//Create DocumentBuilder
DocumentBuilder db = null;
db = dbf.newDocumentBuilder();
//Parse xml to Document object
Document allBooks = null;
allBooks = db.parse(booksFile);
//All data stored in <record> elements
NodeList allRecords = allBooks.getElementsByTagName("record");
//Get number of records for loop
int len = allRecords.getLength();
//Create StringBuffer to hold xml for matching records
StringBuffer buffer = new StringBuffer(256);
buffer.append("<root>");
//Loop through records and grab those with matching category
for(int i=0; i<len; i++){
    //need to access Category node    
    if(allRecords.item(i).getChildNodes().item(5).getTextContent().equals(val) || val.equals("all")){
        //need to loop through childNodes here
        buffer.append("<record>");
        int cLen = allRecords.item(i).getChildNodes().getLength();
        for(int j=0; j<cLen; j++){
            if(allRecords.item(i).getChildNodes().item(j).getNodeName().equals("#text"))
                    continue;//don't need/want text nodes
            //populate StringBuffer w/ matches
            buffer.append("<");
            buffer.append(allRecords.item(i).getChildNodes().item(j).getNodeName());
            buffer.append(">");
            buffer.append(allRecords.item(i).getChildNodes().item(j).getTextContent());
            buffer.append("</");
            buffer.append(allRecords.item(i).getChildNodes().item(j).getNodeName());
            buffer.append(">");
        }
        buffer.append("</record>");
    }
}
buffer.append("</root>");
//Write StringBuffer to response
out.print(buffer.toString());
%>

The only difference from the previous version is the ability to handle the 'all' value in the drop down box:

if(allRecords.item(i).getChildNodes().item(5).getTextContent().equals(val) || val.equals("all"))

13. To finish the script in index.html, add the following code:

	     var cartReq = new xhr();
cartReq.onreadystatechange = processCart;
function addToCart(isbn){
//send request
sendReq(cartReq,'get','shoppingCart.jsp?isbn=' + isbn);
}
function processCart(){
//draw cart
if(cartReq.readyState == 4){
if(cartReq.status == 200){
document.getElementById('cart').style.display = 'block';
document.getElementById('total').style.display = 'block';
var currentCart = document.getElementById('cart').innerHTML + '<br />';
var records = cartReq.responseXML.documentElement.getElementsByTagName('record');
var len = records.length;
for(var i=0; i<len; i++){
var data = records[i].childNodes;
var dLen = data.length;
for(var j=0; j<dLen; j++){
if(data[j].nodeName == '#text')
continue;
if(data[j].nodeName == 'Title')
currentCart += data[j].firstChild.nodeValue + '&nbsp;';
if(data[j].nodeName == 'Price'){
currentCart += '$' + data[j].firstChild.nodeValue + '&nbsp;';
//convert current total to number
var currTotal = document.getElementById('total').innerHTML + '';
currTotal = currTotal.substring(1,currTotal.length);
currTotal = parseFloat(currTotal);
var bookPrice = parseFloat(data[j].firstChild.nodeValue + '');
document.getElementById('total').innerHTML = '$' + (currTotal + bookPrice);
}
}
}
}
document.getElementById('cart').innerHTML = currentCart;

}
}

The above code is nearly identical to the beginning of the script. It first creates an XMLHttpRequest object named cartReq (for cart request) and assigns processCart to the event handler for the readyState property. The addToCart() function sends the request to shoppingCart.jsp (which we'll write in a moment). The processCart() function grabs the results of the responseXML object and creates a display string for the shopping cart and the total. The shopping cart only displays the Title and Price of each book in the cart.

14. Right-click the Web Pages node and choose New > JSP... Name the file shoppingCart and click Finish. Replace the code with the following:

<%@page contentType="text/xml"%>
<%@page pageEncoding="UTF-8"%>
<%@page import="javax.xml.parsers.*,org.w3c.dom.*,java.io.*"  %>


<%
//Grab ISBN
String val = request.getParameter("isbn");
//Get reference to xml file
File booksFile = new File(application.getRealPath("/allBooks.xml"));
//Create DocumentBuilderFactory
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
//Create DocumentBuilder
DocumentBuilder db = null;
db = dbf.newDocumentBuilder();
//Parse xml to Document object
Document allBooks = null;
allBooks = db.parse(booksFile);
//All data stored in <record> elements
NodeList allRecords = allBooks.getElementsByTagName("record");
//Get number of records for loop
int len = allRecords.getLength();
//Create StringBuffer to hold xml for matching records
StringBuffer buffer = new StringBuffer(256);
buffer.append("<root>");
//Loop through records and grab those with matching isbn - should be a single record
for(int i=0; i<len; i++){
    //need to access isbn node    
    if(allRecords.item(i).getChildNodes().item(1).getTextContent().equals(val)){
        //need to loop through childNodes here
        buffer.append("<record>");
        int cLen = allRecords.item(i).getChildNodes().getLength();
        for(int j=0; j<cLen; j++){
            if(allRecords.item(i).getChildNodes().item(j).getNodeName().equals("#text"))
                    continue;//don't need/want text nodes
            //populate StringBuffer w/ match
            buffer.append("<");
            buffer.append(allRecords.item(i).getChildNodes().item(j).getNodeName());
            buffer.append(">");
            buffer.append(allRecords.item(i).getChildNodes().item(j).getTextContent());
            buffer.append("</");
            buffer.append(allRecords.item(i).getChildNodes().item(j).getNodeName());
            buffer.append(">");
        }
        buffer.append("</record>");
    }
}
buffer.append("</root>");
//Write StringBuffer to response
out.print(buffer.toString());
%>

ShoppingCart.jsp is nearly identical to filterBooks.jsp except that it checks the value in the ISBN node to create matches. Since the ISBN is guaranteed to be unique, only a single record can be returned.

15. When the page is first loaded, it should display all of the books. Add an onLoad event to the <body> element:

<body onLoad="sendReq(catReq,'get',getCategory('all'))">

16. Right-click the index.html file and choose Run File. You should be able to manipulate the drop down and add books to the shopping cart.

You can download all of the code for this project here: AjaxBooks2.zip as a Netbeans Project. Just unzip the file and Open the project from Netbeans.