Week 8
CREATE TABLE product_category (
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL
);
INSERT INTO product_category (name) VALUES
('Bean Bag Toy'),
('Teddy Bear'),
('Doll');
ALTER TABLE products ADD category_id INT UNSIGNED NOT NULL DEFAULT 1;
ALTER TABLE products ADD FOREIGN KEY (category_id) REFERENCES product_category(id);
-- Bean Bag Toys (category_id = 1)
INSERT INTO Products (prod_id, vend_id, prod_name, prod_price, prod_desc, category_id)
VALUES
('BBT001', 'FRB01', 'Comfy Bean Bag Toy', 25.99, 'A soft and comfortable bean bag toy for kids.', 1),
('BBT002', 'FRB01', 'Fun Bean Bag Toy', 19.99, 'A playful and colorful bean bag toy for young children.', 1),
('BBT003', 'FRB01', 'Bean Bag Buddy', 29.99, 'A friendly and plush bean bag toy with a cute face.', 1),
('BBT004', 'FRB01', 'Squishy Bean Bag Toy', 24.99, 'A squishy and soft bean bag toy that hugs back.', 1),
('BBT005', 'FRB01', 'Plush Bean Bag Toy', 34.99, 'A large, plush bean bag toy perfect for cuddling.', 1);
-- Teddy Bears (category_id = 2)
INSERT INTO Products (prod_id, vend_id, prod_name, prod_price, prod_desc, category_id)
VALUES
('TBE001', 'BRS01', 'Classic Teddy Bear', 40.00, 'A traditional teddy bear with soft fur and a red bow.', 2),
('TBE002', 'BRS01', 'Plush Teddy Bear', 45.50, 'A cuddly teddy bear with extra plush fur for comfort.', 2),
('TBE003', 'BRE02', 'Teddy Bear with Heart', 50.00, 'A teddy bear holding a heart-shaped pillow.', 2),
('TBE004', 'BRE02', 'Teddy Bear Family', 80.00, 'A set of three teddy bears of different sizes for family playtime.', 2),
('TBE005', 'BRS01', 'Sleepy Teddy Bear', 37.99, 'A sleepy teddy bear with a soft nightcap for bedtime comfort.', 2);
-- Dolls (category_id = 3)
INSERT INTO Products (prod_id, vend_id, prod_name, prod_price, prod_desc, category_id)
VALUES
('DOL001', 'DLL01', 'Classic Doll', 22.99, 'A timeless doll with long hair and a beautiful dress.', 3),
('DOL002', 'DLL01', 'Baby Doll', 19.99, 'A soft baby doll with a bottle and pacifier.', 3),
('DOL003', 'DLL01', 'Fashion Doll', 29.99, 'A stylish fashion doll with interchangeable outfits.', 3),
('DOL004', 'DLL01', 'Princess Doll', 35.50, 'A princess doll with a sparkling gown and tiara.', 3),
('DOL005', 'DLL01', 'Talking Doll', 44.99, 'An interactive doll that talks and sings songs.', 3);
SELECT p.prod_id, p.prod_name, p.prod_price, p.prod_desc, c.id AS category_id, c.name AS category_name
FROM products AS p JOIN product_category AS c
WHERE p.category_id = c.id;
SELECT p.prod_id, p.prod_name, p.prod_price, p.prod_desc, c.id AS category_id, c.name AS category_name
FROM products AS p JOIN product_category AS c
WHERE p.category_id = c.id
AND '1,2' LIKE CONCAT('%', p.category_id , '%');
SELECT p.prod_id, p.prod_name, p.prod_price, p.prod_desc, c.id AS category_id, c.name AS category_name
FROM products AS p JOIN product_category AS c
WHERE p.category_id = c.id
LIMIT 5 OFFSET 0;
DROP PROCEDURE sp_get_all_products;
CREATE PROCEDURE sp_get_all_products(
IN p_limit int,
IN p_offset int,
IN p_category_id VARCHAR(255)
)
BEGIN
SELECT p.prod_id, p.prod_name, p.prod_price, p.prod_desc, c.id AS category_id, c.name AS category_name
FROM products AS p JOIN product_category AS c
WHERE p.category_id = c.id
AND (
CASE
WHEN p_category_id <> ''
THEN p_category_id LIKE CONCAT('%', p.category_id , '%')
ELSE TRUE
END
)
LIMIT p_limit OFFSET p_offset;
END;
public class Product {
// code omitted
private int categoryId;
private String categoryName;
// This constructor is for products on the shop page
public Product(String id, String name, double price, String description, int categoryId, String categoryName) {
// code omitted
this.categoryId = categoryId;
this.categoryName = categoryName;
}
// code omitted
public int getCategoryId() {
return categoryId;
}
public void setCategoryId(int categoryId) {
this.categoryId = categoryId;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
}
// This method get products for the Shop page.
public static List<Product> getProducts(int limit, int offset, String categories) {
List<Product> products = new ArrayList<>();
try(Connection connection = getConnection()) {
CallableStatement statement = connection.prepareCall("{CALL sp_get_all_products(?,?,?)}");
statement.setInt(1, limit);
statement.setInt(2, offset);
statement.setString(3, categories);
ResultSet rs = statement.executeQuery();
while(rs.next()) {
String id = rs.getString("prod_id");
String name = rs.getString("prod_name");
double price = rs.getDouble("prod_price");
String description = rs.getString("prod_desc");
int categoryId = rs.getInt("category_id");
String categoryName = rs.getString("category_name");
products.add(new Product(id, name, price, description, categoryId, categoryName));
}
} catch(SQLException e) {
throw new RuntimeException("Database error - " + e.getMessage());
}
return products;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
int limit = 5;
int offset = 0;
String categories = "1,2,3";
List<Product> products = ProductDAO.getProducts(limit, offset, categories);
req.setAttribute("products", products);
req.getRequestDispatcher("WEB-INF/ecommerce/shop.jsp").forward(req, resp);
}
public static void main(String[] args) {
getProducts(5, 0, "1,2,3").forEach(System.out::println);
}
SELECT c.id, c.name, COUNT(p.category_id) AS num_products
FROM products AS p JOIN product_category AS c
ON p.category_id = c.id
GROUP BY c.id, c.name
ORDER BY c.name;
CREATE PROCEDURE sp_get_product_categories()
BEGIN
SELECT c.id, c.name, COUNT(p.category_id) AS num_products
FROM products AS p JOIN product_category AS c
ON p.category_id = c.id
GROUP BY c.id, c.name
ORDER BY c.name;
END;
public class ProductCategory {
private int id;
private String name;
private int numProducts;
}
public static List<ProductCategory> getAllCategories() {
List<ProductCategory> categories = new ArrayList<>();
try (Connection connection = getConnection();
CallableStatement statement = connection.prepareCall("{CALL sp_get_product_categories()}");
ResultSet resultSet = statement.executeQuery();
) {
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int numProducts = resultSet.getInt("num_products");
categories.add(new ProductCategory(id, name, numProducts));
}
} catch(SQLException e) {
System.out.println(e.getMessage());
}
return categories;
}
List<ProductCategory> productCategories = ProductDAO.getAllCategories();
req.setAttribute("productCategories", productCategories);
public static void main(String[] args) {
getAllCategories().forEach(System.out::println);
}
<!-- Right sidebar START -->
<div class="col-lg-3">
<!-- Responsive offcanvas body START -->
<div class="offcanvas-lg offcanvas-end" tabindex="-1" id="offcanvasSidebar">
<div class="offcanvas-header bg-light">
<h5 class="offcanvas-title" id="offcanvasNavbarLabel">Advanced Filter</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#offcanvasSidebar" aria-label="Close"></button>
</div>
<div class="offcanvas-body p-3">
<form>
<!-- Category START -->
<div class="card card-body shadow p-4 mb-4">
<!-- Title -->
<h4 class="mb-2">Category</h4>
<!-- Checkbox -->
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="filter-1">
<label class="form-check-label" for="filter-1">Option 1</label>
</div>
<!-- Checkbox -->
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="filter-2">
<label class="form-check-label" for="filter-2">Option 2</label>
</div>
<!-- Checkbox -->
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="filter-3">
<label class="form-check-label" for="filter-3">Option 3</label>
</div>
<!-- Checkbox -->
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="filter-4">
<label class="form-check-label" for="filter-4">Option 4</label>
</div>
</div>
<!-- Category END -->
<!-- Limit START -->
<div class="card card-body shadow p-4">
<!-- Title -->
<h4 class="mb-2">Show</h4>
<div class="btn-group" role="group" aria-label="Show radio toggle button group">
<input type="radio" class="btn-check" id="show-5" autocomplete="off">
<label class="btn btn-outline-success" for="show-5">5</label>
<input type="radio" class="btn-check" id="show-10" autocomplete="off">
<label class="btn btn-outline-success" for="show-10">10</label>
<input type="radio" class="btn-check" id="show-20" autocomplete="off">
<label class="btn btn-outline-success" for="show-20">20</label>
</div>
</div><!-- Limit END -->
<!-- Submit Button -->
<div class="d-grid text-center m-4">
<button type="submit" class="btn btn-primary">Filter Results</button>
</div>
</form><!-- Form End -->
</div><!-- offcanvas-body End -->
</div><!-- Responsive offcanvas body END -->
</div><!-- Right sidebar END -->
<div class="container py-4">
<div class="col d-flex justify-content-between align-items-center">
<h2 class="mb-4">Shop the best selection of toys around!</h2>
<!-- Responsive toggler START -->
<button class="btn btn-primary d-lg-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasSidebar" aria-controls="offcanvasSidebar">
<i class="bi bi-list fs-4"></i>
</button>
<!-- Responsive toggler END -->
</div>
<div class="row">
<!-- Main content START -->
<div class="col-lg-9">
<div class="row g-4">
<c:forEach items="${products}" var="product">
<%-- 12 means full-width, 6 means half-width, 4 means one-third width, 3 means one-forth width --%>
<div class="col-xs-12 col-sm-6 col-md-4">
<div class="card shadow-sm">
<div class="card-header py-2">
<h4 class="my-0 text-center">${product.name}</h4>
</div>
<div class="card-body">
<p class="card-text">${product.description}</p>
<div class="d-flex justify-content-between align-items-center">
<small class="fw-bold"><fmt:formatNumber value="${product.price}" type="currency" /></small>
<a href="${appURL}/add-to-cart?prod_id=${product.id}" class="btn btn-secondary">Add to Cart</a>
</div>
</div>
</div>
</div>
</c:forEach>
</div>
</div><!-- Main content END -->
<%@include file="shop-sidebar.jspf"%>
</div><!-- Row END -->
</div><!-- Container END -->
<form method="GET" action="${appURL}/shop">
<c:forEach items="${productCategories}" var="category">...</c:forEach>
<label class="form-check-label" for="filter-1">${category.name} (${category.numProducts})</label>
value="${category.id}"
name="categories"
value="5"
value="10"
value="20"
name="limit"
checked
attribute to the second radio input.String limitValue = req.getParameter("limit");
int limit = 10;
try {
limit = Integer.parseInt(limitValue);
} catch(NumberFormatException e) {
}
int offset = 0; // Will be implemented in a future lesson on pagination
String[] categoryValues = req.getParameterValues("categories");
String categories = "";
if(categoryValues != null && categoryValues.length > 0) {
categories = String.join(",", categoryValues);
}
req.setAttribute("categories", categories);
req.setAttribute("limit", limit);
<c:if test="${fn:contains(categories, category.id)}">checked</c:if>
Add c:if statements to each radio button input to select the last selected item.
<c:if test="${limit eq 5}">checked</c:if>
<c:if test="${limit eq 10}">checked</c:if>
<c:if test="${limit eq 20}">checked</c:if>
Run and test the program.
<p class="badge rounded-pill text-bg-secondary"><a class="text-white" href="${appURL}/shop?categories=${product.categoryId}">${product.categoryName}</a></p>