Implementing the Shopping Cart

I l @ ve RuBoard

With your products on display, you can start actually allowing people to buy them. To do this, you're going to need to implement a shopping cart. If you want, you can refer back to the design of the shopping cart in Chapter 4, "The Sample Application Functional Requirements Document."

A cart is essentially a container that you can put products into, with a few quirks . For example, putting a second copy of an item into a cart shouldn't make two entries; it should make a single item with a quantity of 2.

The cart has session persistence, like the customer. Unlike the customer object however, it doesn't get its contents from a database lookup. Eventually, when you get to filling and spilling content in Chapter 11, "Intersession Persistence," it will do this, but it doesn't for now. Instead, the cart has its contents modified by the actions of the customer during a shopping session.

The actual implementation of the cart is pretty simple, so the best approach is just to dive right into the code in Listings 10.1 and 10.2.

Listing 10.1 Cart.java
 package com.bfg.cart; import java.util.Vector; import java.util.HashMap; import java.util.Iterator; import java.text.NumberFormat; import org.apache.turbine.util.Log; import com.bfg.product.Product; public class Cart {     protected HashMap contents = new HashMap();     public Iterator getItems() {      return contents.values().iterator();     }     public CartItem getItem(Product item) {      return (CartItem)contents.get(item);     }     public void addItem(Product item, int quantity) {      if (contents.get(item) != null) {          ((CartItem)contents.get(item)).addQuantity(quantity);      }  else {           CartItem citem = new CartItem();           citem.setQuantity(quantity);           citem.setProduct(item);           contents.put(item, citem);      }     }     public void removeItem(Product item) {      contents.remove(item);     }     public int countItems() {      return contents.size();     }     public double getTotal() {      double total = 0;      Iterator it = contents.values().iterator();      while (it.hasNext()) {          total += ((CartItem)it.next()).getLineItemPrice();      }      return total;     }     public String getTotalString() {      NumberFormat nf = NumberFormat.getCurrencyInstance();      return nf.format(getTotal());     } } 
Listing 10.2 CartItem.java
 package com.bfg.cart; import java.text.NumberFormat; import org.apache.turbine.util.Log; import com.bfg.product.Product; public class CartItem {     private Product pProduct;     private int pQuantity;     private boolean isPromotionItem;     public Product getProduct() {      return pProduct;     }     public void setProduct(Product product) {      pProduct = product;     }     public int getQuantity() {      return pQuantity;     }     public void setQuantity(int quantity) {      pQuantity = quantity;     }     public void addQuantity(int quantity) {      pQuantity += quantity;     }     public double getLineItemPrice() {      return getQuantity() * getProduct().getPrice();     }     public String getLineItemPriceString() {      NumberFormat nf = NumberFormat.getCurrencyInstance();      return nf.format(getLineItemPrice());     } } 

The cart is just a wrapper around a HashMap that holds the product and quantity of each item bought. If you add a couple of helper methods (such as getTotal in Listing 10.1), you can service the mini-cart efficiently (printed cost, number of items, and so on). Helper methods are ones that don't directly contribute to the functioning of the object but that provide a more convenient way to do something that's already implemented. addItem makes sure that it consolidates items if you add something already in the cart.

The CartItem is just a wrapper for the product and quantity; however, it has a flag to indicate that the item was placed in the cart by the successful execution of a promotion. This will become useful later when you implement promotions. Again, you also include a couple of helper functions.

With the cart written, you can implement the Buy button on your product pages (as shown in Listing 10.3).

Listing 10.3 buyit.jsp
 <%@ page import="com.bfg.product.Product" %> <%@ page import="com.bfg.cart.Cart" %> <jsp:useBean id="cart" class="com.bfg.cart.Cart" scope="session"/> <% String ISBN = request.getParameter("ISBN"); Product prod = null; if (ISBN != null) {     prod = Product.findProduct(ISBN);     if (prod != null) {      cart.addItem(prod, 1);     } } %> You added <%= prod %> to your cart, new quantity <%= cart.countItems() %> <SCRIPT> if (window.opener && !window.opener.closed)   window.opener.location.reload(); window.close(); </SCRIPT> 
Listing 10.4 Using the buyit Script
 <a href="buyit.jsp?ISBN=<%= prod.getISBN() %>" TARGET="tempwindow">[Buy It!] </a> 

Basically, the link shown in Listing 10.4 opens a new temporary window that loads up the buyit.jsp page. buyit.jsp (shown in Listing 10.3) adds a product to the cart and then closes itself, usually before the customer even knows that it's open . Before it closes itself, however, the page tells the window that opened it to reload, which causes the mini-cart to refresh. Refinements to the Buy link would include opening a smaller window.

THE RESUBMIT PROBLEM

A problem that crops up consistently in this kind of page occurs when the user hits the Reload button. In a traditional design (and even the design you use for things such as new user creation), you submit back to the same page.

This means that if the user enters data and hits Submit, reloads the page, and answers Yes to the question "Do you want to resubmit the data?", the application executes the action twice. In the case of a shopping cart, this might mean putting twice as many items into the cart, for example.

By using a secondary window to do the actual cart adds and then closing it, you avoid this problem.

Speaking of the mini-cart, you can now implement it in bfgheader .jsp (see Listing 10.5).

Listing 10.5 The Mini-cart in bfgheader.jsp
 <tr>   <td width="50%" height="35"><font size="1">   <font color="#008000">In your   cart:<br>   <%= cart.countItems() %> Item<%= (cart.countItems() == 1)?"":"s" %> totaling <%= cart.getTotalString() %></font>   <a href="http://www.boston.com/sports/redsox">[check out]</a></font></td> </tr> 

useBean is a great little construct that lets you associate an object with a degree of persistence. However, at least as implemented by Tomcat 4, it can cause you problems, too.

The main problem that you can run into occurs if you use it in an included file. For example, it would be nice to have a useBean in the bfgheader file to allow you to look at the cart bean and print the mini-cart values. Unfortunately, several files include bfgheader multiple times (to deal with success or failure). Tomcat can't process this because it tries to insert the useBean twice and gets a syntax error.

An alternative is to use curly-braces around a section of code that wants to reference a bean value and declare a local variable inside the brackets that uses getAttribute explicitly to get the bean value. Because the assignment is made inside the braces, it won't interfere with other code that needs to look at the same bean, even if the same variable name is used (see Listing 10.6 to see the revised bfgheader.jsp ).

Listing 10.6 bfgheader.jsp without useBean
 <%@ page import="java.util.Iterator" %> <%@ page import="com.bfg.cart.Cart" %> <%@ page import="com.bfg.product.Category" %> <% { Cart headercart = (Cart) pageContext.getAttribute("cart", PageContext.SESSION_SCOPE); %> <body> <table border="0" cellpadding="0" cellspacing="0" width="100%">   <tr>     <td width="22%">     <img border="0" src="/bfg/jsp/images/bfglogo.jpg" width="175" height="168"></td>     <td width="78%">     <h1 align="center"><font color="#0000FF">Welcome to Books For Geeks!</font></h1>     </td>   </tr>   <tr>     <td width="22%">     <table border="0" cellpadding="0" cellspacing="0" width="100%">       <tr>         <td width="50%" height="35"><font size="1">         <font color="#008000">In your         cart:<br>     <%= headercart.countItems() %>      Item<%= (headercart.countItems() == 1)?"":"s" %> totaling      <%= headercart.getTotalString() %></font>         <a href="http://www.boston.com/sports/redsox">[check out]</a></font></td>       </tr>     </table>     <p>Departments</p>     <blockquote>       <h5>     <% Category.loadAllCategories(); Iterator it = Category.getCategories().iterator(); while (it.hasNext()) {     String catName = (String) it.next(); %>     <a href="/bfg/jsp/product/Category.jsp?category=<%= catName %>"><%= catName %></a><br>       <%       } %>     </h5>     </blockquote>     <p><a href="/bfg/jsp/shoppingcart.jsp">View Basket</a><br>     <a href="/bfg/jsp/checkout.jsp">Check Out</a><br>     <a href="/bfg/jsp/cust/MyAccount.jsp">My Account</a></p>     <p>&nbsp;</p>     <p>&nbsp;</p>     <p>&nbsp;</td>     <td width="78%" valign="top"> <% }  %> 

Use the ? operator to make sure that you choose the correct plural (0 items, 1 item, 2 items, and so on). The live version of the mini-cart is shown in Figure 10.1.

Figure 10.1. The Mini-cart in operation.

graphics/10fig01.jpg

An interesting subject for debate rears its head at this point. When counting items, do two books of the same type count as one item or two items in the mini-cart? As implemented, they count as one, but you could do it the other way and be just as right. As always, what the customer wants is what the customer should get. In this case, the client wants the cart to count distinct product groups, not quantity.

WATCH OUT FOR <%@ INCLUDE !

If you don't watch yourself, you can get suckered by a quirk of Tomcat when you use included JSP files.

To understand why, remember how JSP works. The JSP files are converted into Java source and then are compiled. When the JSP changes, the Java is automatically recompiled to keep up.

Well, almost automatically. Included content isn't done dynamically at runtime; the Java source produced by Jasper (the JSP-to-Java translator) is done once and literally sticks the included JSP children right into the Java source that it produces. So, even though you might think of each file as its own little Java program, they're all really made into one big one.

When you change an included file (such as bfgheader.jsp), Jasper doesn't notice it, so it doesn't recompile the JSP files that include it. And because your Ant script copies only the JSP files that change, the parent JSP files won't be updated.

This can lead to interesting results. As you move from page to page, you might see one page with the changed content and the next page with the old content.

The only way to make sure that everything gets recompiled is to copy everything over every time you do an ant dist .

This behavior is not a part of the JSP specification, so you'll have to check for yourself to see if a non-Tomcat JSP platform has the same problem.

I l @ ve RuBoard


MySQL and JSP Web Applications. Data-Driven Programming Using Tomcat and MySQL
MySQL and JSP Web Applications: Data-Driven Programming Using Tomcat and MySQL
ISBN: 0672323095
EAN: 2147483647
Year: 2002
Pages: 203
Authors: James Turner

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net