Java 3 - 2025

Week 10

Add to Cart

  • In shop.jsp, convert the Add to Cart hyperlink into a form with a hidden field for the product id.
  • Turn the hyperlink button into a form button.
  • Add a quantity input next to the Add to Cart button. Set the default value to 1.
<div class="d-flex justify-content-between align-items-center">
    <small class="fw-bold"><fmt:formatNumber value="${product.price}" type="currency" /></small>
    <form method="POST" action="${appURL}/add-to-cart" class=" w-75">
        <input type="hidden" name="prod_id" value="${product.id}">
        <div class="input-group">
            <div class="form-floating">
                <input type="number" min="0" class="form-control" id="quantity" name="quantity" value="1">
                <label for="quantity">Qty</label>
            </div>
            <button type="submit" class="btn btn-outline-primary btn-sm">Add to Cart</button>
        </div>
    </form>
</div>

AddToCart servlet

  • Create a new servlet called AddToCart. Add a doPost method. This servlet does not need a doGet method.
  • Get all parameters, display a flash message, and redirect back to the shop page.
  • Do not set any other attributes yet.
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import java.io.IOException;

@WebServlet("/add-to-cart")
public class AddToCart extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String prodId = req.getParameter("prod_id");
        String quantityStr = req.getParameter("quantity");

        HttpSession session = req.getSession();
        session.setAttribute("flashMessageSuccess", "Added to cart successfully");
        resp.sendRedirect(resp.encodeRedirectURL(req.getContextPath() + "/shop"));
    }
}

Get Product Stored Procedure

  • Create a stored procedure to get a single product by its prod_id.
CREATE PROCEDURE sp_get_product(IN p_prod_id varchar(10))
BEGIN
    SELECT prod_id, prod_name, prod_price, prod_desc
    FROM products
    WHERE prod_id = p_prod_id;
END;

Product Constructor

  • Create a Product constructor for shopping cart data.
// This constructor is for products in the shopping cart
public Product(String id, String name, double price, String description) {
    this.id = id;
    this.name = name;
    this.price = price;
    this.description = description;
}

Get Product Method

  • Create a method in the Product DAO to get a single product by its prod_id.
public static Product get(String prod_id) {
    Product product = null;
    try(Connection connection = getConnection()) {
        CallableStatement statement = connection.prepareCall("{call sp_get_product(?)}");
        statement.setString(1, prod_id);
        ResultSet resultSet = statement.executeQuery();
        if (resultSet.next()) {
            String prodId = resultSet.getString("prod_id");
            String prodName = resultSet.getString("prod_name");
            double prodPrice = resultSet.getDouble("prod_price");
            String prodDesc = resultSet.getString("prod_desc");
            product = new Product(prodId, prodName, prodPrice, prodDesc);
        }
    } catch(SQLException e) {
        throw new RuntimeException(e);
    }
    return product;
}
  • Add code to the main method to test this function. The Classic Doll should print.
public static void main(String[] args) {
    System.out.println(get("DOL001"));
}

AddToCart, validate product

  • Update the AddToCart doPost method to validate the product. If it is null, display an error message.
@WebServlet("/add-to-cart")
public class AddToCart extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String prodId = req.getParameter("prod_id");
        String quantityStr = req.getParameter("quantity");
        
        boolean errorFound = false;
        String errorMsg = "";
        Product product = ProductDAO.get(prodId);
        if(product == null){
            errorFound = true;
            errorMsg += "Invalid product\n";
        }

        HttpSession session = req.getSession();
        if(errorFound){
            session.setAttribute("flashMessageDanger", errorMsg);
        } else {
            session.setAttribute("flashMessageSuccess", "Added to cart successfully");
        }

        resp.sendRedirect(resp.encodeRedirectURL(req.getContextPath() + "/shop"));
    }
}
  • Clicking an Add to Cart button should display the success message.
  • If you edit the HTML to have a different hidden prod_id, the error message should display.

AddToCart, validate quantity

  • Update the AddToCart doPost method to validate the quantity. If it is not valid, display an error message.
@WebServlet("/add-to-cart")
public class AddToCart extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String prodId = req.getParameter("prod_id");
        String quantityStr = req.getParameter("quantity");

        boolean errorFound = false;
        String errorMsg = "";
        Product product = ProductDAO.get(prodId);
        if(product == null){
            errorFound = true;
            errorMsg += "Invalid product\n";
        }

        int quantity = 0;
        try {
            quantity = Integer.parseInt(quantityStr);
            if(quantity < 0){
                errorFound = true;
                errorMsg += "Quantity cannot be negative\n";
            }
        } catch (NumberFormatException e) {
            errorFound = true;
            errorMsg += "Invalid quantity";
        }

        HttpSession session = req.getSession();
        if(errorFound){
            session.setAttribute("flashMessageDanger", errorMsg);
        } else {
            session.setAttribute("flashMessageSuccess", "Added to cart successfully");
        }

        resp.sendRedirect(resp.encodeRedirectURL(req.getContextPath() + "/shop"));
    }
}
  • Setting a valid quantity and clicking the Add to Cart button should display the success message.
  • If you edit the HTML to have an invalid quantity, the error message should display.

ShoppingCart class

  • Create a new model class called Shopping Cart
  • Add one instance variable, a Map<Product, Integer>, used to associate a product with an integer (the quantity).
  • When adding a product, check if it exists before adding it.
import java.util.HashMap;
import java.util.Map;

public class ShoppingCart {
    // Map to associate a product to quantity
    private Map<Product, Integer> cartContents;

    public ShoppingCart() {
        cartContents = new HashMap<>();
    }
    
    // Method to add a product to a user's cart
    public void addProduct(Product product, int quantity) {
        if(product == null) {
            throw new IllegalArgumentException("Product cannot be null");
        }
        if(quantity < 1) {
            throw new IllegalArgumentException("Quantity cannot be less than 1");
        }
        contents.put(product, contents.getOrDefault(product, 0) + quantity);
    }

    // Method to view the cart contents
    public Map<Product, Integer> getCartContents() {
        return cartContents;
    }
}

Shopping Cart main method

  • Write a main method to test the addProduct and getCartContents methods.
  • Instantiate a ShoppingCart.
  • Get products from the DAO.
  • Add products and quantities to the shopping cart.
  • Write a loop to display all shopping cart contents.
  • Even though product1 and product3 are the same, they display separately.
public static void main(String[] args) {
    ShoppingCart cart = new ShoppingCart();
    Product product1 = ProductDAO.get("DOL001");
    Product product2 = ProductDAO.get("BR02");
    Product product3 = ProductDAO.get("DOL001");
    cart.addProduct(product1, 1);
    cart.addProduct(product2, 2);
    cart.addProduct(product3, 3);
    cart.getCartContents().entrySet().forEach(cartItem -> {
        Product product = cartItem.getKey();
        int quantity = cartItem.getValue();
        double price = product.getPrice();
        double totalPrice = price * quantity;
        System.out.printf("%s, Qty: %d, Price: %.2f, Total: %.2f\n", product.getName(), quantity, price, totalPrice);
    });
}

Product Class

  • Generate an equals and hashCode method to determine if two Product objects are equal.
  • Re-run the main method in the ShoppingCart class. Products 1 and 3 are now considered the same product.
@Override
public boolean equals(Object o) {
    if (o == null || getClass() != o.getClass()) return false;
    Product product = (Product) o;
    return Double.compare(price, product.price) == 0 && categoryId == product.categoryId && Objects.equals(id, product.id) && Objects.equals(name, product.name) && Objects.equals(description, product.description) && Objects.equals(vendorId, product.vendorId) && Objects.equals(vendorName, product.vendorName) && Objects.equals(categoryName, product.categoryName);
}

@Override
public int hashCode() {
    return Objects.hash(id, name, price, description, vendorId, vendorName, categoryId, categoryName);
}

AddToCart, add item to cart

  • Write code to get a ShoppingCart object from the session.
  • If the object doesn't exist, instantiate a new one.
  • Add the new product and quantity.
  • Display the success message.
HttpSession session = req.getSession();
if(errorFound){
    session.setAttribute("flashMessageDanger", errorMsg);
} else {
    ShoppingCart cart = (ShoppingCart) session.getAttribute("cart");
    if(cart == null){
        cart = new ShoppingCart();
    }
    cart.addProduct(product, quantity);
    session.setAttribute("cart", cart);
    session.setAttribute("flashMessageSuccess", "Added to cart successfully");
}

ShoppingCart getProductCount

  • In the ShoppingCart class, create a new method to get the total shopping cart count and total cost.
  • In the first example, the for-each loop uses the .values() method to loop through the Map's integers.
  • In the second example, the for-each loop uses the .entrySet() method to loop through the Map's products and integers.
// Method to return the total count of products in the cart
public int getTotalProductCount() {
    int total = 0;
    for (int quantity : cartContents.values()) {
        total += quantity;
    }
    return total;
}

// Method to calculate the total cost of products in the cart
public double getTotalCost() {
    double totalCost = 0.0;
    for (Map.Entry<Product, Integer> entry : cartContents.entrySet()) {
        Product product = entry.getKey();
        int quantity = entry.getValue();
        totalCost += product.getPrice() * quantity;
    }
    return totalCost;
}

top-navigation

  • Replace the unused search bar with a Shopping Cart button.
  • The icon comes from Bootstrap Icons.
  • The first c:if tag will display the product count if the cart attribute is set.
  • The second c:if tag will display and format the total cost if the cart attribute is set.
<a href="${appURL}/cart" class="btn btn-primary me-2">
    <i class="bi bi-cart"></i> 
    <c:if test="${not empty cart.totalProductCount}">${cart.totalProductCount}, </c:if> 
    <c:if test="${not empty cart.totalCost}"><fmt:formatNumber value="${cart.totalCost}" type="currency" /></c:if>
</a>

End Day 19

Cart Servlet

  • Create a new Cart servlet class with a doGet Method.
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet("/cart")
public class Cart extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setAttribute("pageTitle", "Cart");
        req.getRequestDispatcher("WEB-INF/ecommerce/cart.jsp").forward(req, resp);
    }
}

cart.jsp

  • Create a new cart.jsp file.
  • Add HTML to display all items in the cart, allow the user to edit the quantity, and see a summary of contents.
  • Update the template's background color.
  • Write a c:choose tag to display the cart count.
<section class="h-100 h-custom bg-secondary">

 <!-- Code omitted -->
 
    <div class="d-flex justify-content-between align-items-center mb-5">
        <h1 class="fw-bold mb-0">Shopping Cart</h1>
        <h6 class="mb-0 text-muted">
            <c:choose>
                <c:when test="${not empty cart.totalProductCount}">${cart.totalProductCount}&nbsp;${cart.totalProductCount eq 1 ? 'item' : 'items'}</c:when>
                <c:otherwise>There are no products in your cart</c:otherwise>
            </c:choose>
        </h6>
    </div>
                                    
 <!-- Code omitted -->

    <div class="pt-5">
        <h6 class="mb-0"><a href="${appURL}/shop" class="text-body"><i
                class="fas fa-long-arrow-alt-left me-2"></i>Back to shop</a></h6>
    </div>
                                    
 <!-- Code omitted -->
 
</section>

cart.jsp

  • Keep one of the product rows and horizontal rules.
  • Add a c:forEach tag to loop through each product in the cart.
    • ${cart.getContents} returns the Map object
    • "item" refers to an entry in the Map
    • "item.key" returns the Product
    • "item.value" returns the Integer
  • Our products don't have images. Remove that column.
  • Keep one of the h6 headings.
  • Replace the plus/minus buttons and input with a form similar to the shop page products.
  • Display the price and a trash can icon.
  • Update column widths.
<c:forEach items="${cart.cartContents}" var="item">
    <div class="row mb-4 d-flex justify-content-between align-items-center">
        <div class="col-md-6">
            <h6 class="mb-0">${item.key.name}</h6>
        </div>
        <div class="col-md-3">
            <form method="POST" action="${appURL}/cart">
                <input type="hidden" name="prod_id" value="${item.key.id}">
                <div class="input-group">
                    <div class="form-floating">
                        <input type="number" min="0" class="form-control" id="quantity" name="quantity" value="${item.value}">
                        <label for="quantity">Qty</label>
                    </div>
                    <button type="submit" class="btn btn-outline-primary btn-sm">Update</button>
                </div>
            </form>
        </div>
        <div class="col-md-2 text-end">
            <h6 class="mb-0"><fmt:formatNumber value="${item.key.price}" type="currency" /></h6>
        </div>
        <div class="col-md-1 text-end">
            <a href="#!" class="text-muted"><i class="bi bi-trash"></i></a>
        </div>
    </div>

    <hr class="my-4">
</c:forEach>

Summary Column

  • Use a c:if tag to only display the Summary if the cart is set.
  • Remove the Shipping input and replace it with Free Shipping.
    • Add shipping options to exceed expectations.
  • Remove the Coupon Code.
    • Add coupon codes to exceed expectations.
  • Update the button text.
<c:if test="${not empty cart}">
    <div class="p-5">
        <h3 class="fw-bold mb-5 mt-2 pt-1">Summary</h3>
        <hr class="my-4">
        
        <div class="d-flex justify-content-between mb-4">
            <h5 class="text-uppercase">subtotal</h5>
            <h5><fmt:formatNumber value="${cart.totalCost}" type="currency" /></h5>
        </div>

        <c:set var="shipping" value="0" /><%-- Remove if you add a shipping calculation to the doPost method of the Cart servlet --%>
        <div class="d-flex justify-content-between mb-4">
            <h5 class="text-uppercase mb-3">Free Shipping</h5>
            <h5><fmt:formatNumber value="0" type="currency" /></h5>
        </div>

        <hr class="my-4">

        <div class="d-flex justify-content-between mb-5">
            <h5 class="text-uppercase">Total price</h5>
            <h5><fmt:formatNumber value="${cart.totalCost + shipping}" type="currency" /></h5>
        </div>

        <button  type="button" data-mdb-button-init data-mdb-ripple-init class="btn btn-dark btn-block btn-lg"
                 data-mdb-ripple-color="dark">Check out</button>

    </div>
</c:if>

Update Cart Items

  • Open the ShoppingCart model. Add method called updateProduct.
  • Validate the product and quantity.
  • Use the Map's put() method to update the product's quantity.
public void updateProduct(Product product, int quantity) {
    if(product == null) {
      	throw new IllegalArgumentException("Product cannot be null");
    }
    if(quantity < 1) {
      	throw new IllegalArgumentException("Quantity cannot be less than 1");
    }
    contents.put(product, quantity);
}

Update Quantity Button

  • In cart.jsp, add a hidden input with an action name "update".
  • Call the Cart servlet's doPost method when the button is pressed.
<form method="POST" action="${appURL}/cart" class="d-flex justify-content-center align-items-end">
    <input type="hidden" name="prod_id" value="${entry.key.id}">
    <input type="hidden" name="action" value="update">
    <div class="input-group w-50">
        <div class="">
            <%-- entry.value refers to the Integer, the product quantity--%>
            <input id="quantity" min="0" name="quantity" value="${entry.value}" type="number"
                   class="form-control form-control-sm" />

        </div>
    </div>
    <button type="submit" class="btn btn-outline-primary btn-sm">Update</button>
</form>

Delete Cart Items

  • Open the ShoppingCart model. Add method called deleteProduct.
  • Validate the product and quantity.
  • Use the Map's containsKey() method to verify that the product exists.
  • Use the Map's remove() method to delete the product key.
public void deleteProduct(Product product) {
    if(product == null) {
        throw new IllegalArgumentException("Product cannot be null");
    }
    if(contents.containsKey(product)) {
        contents.remove(product);
    }
}

Delete Cart Items

  • In cart.jsp, convert the trash can icon into a form that makes a post request to the Cart servlet.
  • Add a hidden input with an action name "delete".
<form method="POST" action="${appURL}/cart" class="d-flex justify-content-center align-items-end">
    <input type="hidden" name="prod_id" value="${entry.key.id}">
    <input type="hidden" name="action" value="delete">
    <button type="submit" class="text-muted"><i class="bi bi-trash"></i></button>
</form>

Cart Servlet doPost

  • Create a doPost method that gets the action and prod_id parameters. 
  • Use the product id to get the product.
  • Get the ShoppingCart from the session. Continue only if both exist.
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String action = req.getParameter("action");
    String prodId = req.getParameter("prod_id");
    Product product = ProductDAO.getProduct(prodId);
    HttpSession session = req.getSession();
    ShoppingCart cart = (ShoppingCart)session.getAttribute("cart");
    if(cart != null && product != null) {
        
    }
    req.setAttribute("pageTitle", "Cart");
    req.getRequestDispatcher("WEB-INF/ecommerce/cart.jsp").forward(req, resp);
}

Cart Servlet doPost

  • If the user submitted the update form, validate the quantity, then call the ShoppingCart's updateProduct method.
  • If the user submitted the delete form, call the ShoppingCart's deleteProduct method.
  • Update the cart in the session.
if (action.equals("update")) {
    String quantityStr = req.getParameter("quantity");
    int quantity = 1;
    try {
        quantity = Integer.parseInt(quantityStr);
        if (quantity < 1) {
            quantity = cart.getContents().get(product);
        }
    } catch (NumberFormatException e) {
        // If error, keep whatever is in the cart
        quantity = cart.getContents().get(product);
    }
    cart.updateProduct(product, quantity);
} else if (action.equals("delete")) {
    cart.deleteProduct(product);
}
session.setAttribute("cart", cart);

2026

  • When a blank is favorited, their button should look different, indicating it is already favorited.