Marc Hauschildt
Web Technologies and Computer Software Development Instructor at Kirkwood Community College in Cedar Rapids, IA.
Week 9
Instead of building pagination into the Shop Controller it would make more sense to build it in the shared class then call it as needed for pages that display lists.
<nav aria-label="...">
<ul class="pagination">
<li class="page-item disabled">
<a class="page-link">Previous</a>
</li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item active" aria-current="page">
<a class="page-link" href="#">2</a>
</li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item">
<a class="page-link" href="#">Next</a>
</li>
</ul>
</nav>
<div class="row">
<div class="col-lg-9">
<div class="col d-flex justify-content-between align-items-center">
<p class="lead">${fn:length(products)} product${fn:length(products) != 1 ? "s" : ""} shown</p>
<%@include file="/WEB-INF/pagination.jspf"%>
</div>
<div class="row g-4">
<c:forEach items="${products}" var="product">
<li class="page-item <c:if test="${page eq 1}">active</c:if>" <c:if test="${page eq 1}">aria-current="page"</c:if>>
<a class="page-link" href="${appURL}/shop?page=1">1</a>
</li>
<li class="page-item <c:if test="${page eq 2}">active</c:if>" <c:if test="${page eq 1}">aria-current="page"</c:if>>
<a class="page-link" href="${appURL}/shop?page=2">2</a>
</li>
<li class="page-item <c:if test="${page eq 3}">active</c:if>" <c:if test="${page eq 1}">aria-current="page"</c:if>>
<a class="page-link" href="${appURL}/shop?page=3">3</a>
</li>
String pageStr = req.getParameter("page");
int page = 1;
try {
page = Integer.parseInt(pageStr);
} catch(NumberFormatException e) {
}
req.setAttribute("page", page);
Because we have the limit set to 10, that means:
Page 1 will show products 0 to 9
Page 2 will show products 10 to 19
Page 3 will show products 20 to 29
The offset variable represents the first product we want to display on each page. In this case, 0, 10, and 20. We need to write a mathematical expression to calculate those values.
int offset = (page - 1) * limit;
page | limit | (page - 1) | (page - 1) * limit |
---|---|---|---|
1 | 10 | 0 | 0 |
2 | 10 | 1 | 10 |
3 | 10 | 2 | 20 |
Create a new stored procedure that gets the total number of products based on the ones that are being selected by the filter.
This is basically the same as the sp_get_all_productrs stored procedure, except it doesn't have the limit and offset. The SELECT statement only returns the row count.
CREATE PROCEDURE sp_get_total_products(
IN p_category_id VARCHAR(255)
)
BEGIN
SELECT COUNT(*) as total_products
FROM products JOIN product_category
WHERE product_category.id = products.category_id
AND (
CASE
WHEN p_category_id <> ''
THEN p_category_id LIKE CONCAT('%', products.category_id , '%')
ELSE TRUE
END
);
END;
Calling this will return the total product count.
CALL sp_get_total_products('');
Calling this will return the count of two categories.
CALL sp_get_total_products('1,2');
Create a method that returns the total number of products.
public static int getProductCount(String categories) {
try(Connection connection = getConnection();
CallableStatement statement = connection.prepareCall("{CALL sp_get_total_products(?)}");
) {
statement.setString(1, categories);
try(ResultSet resultSet = statement.executeQuery();) {
if (resultSet.next()) {
return resultSet.getInt("total_products");
}
}
} catch(SQLException e) {
System.out.println(e.getMessage());
}
return 0;
}
Run this code in the main method to see the number of products returned.
System.out.println(getProductCount(""));
Calling this will return the count of two categories.
System.out.println(getProductCount("1,2"));
Call the ProductDAO.getProductCount method to get the total records available.
Then, write an expression to calculate the total number of pages.
For example, if there are 24 products and I want to display 10 per page, 24 divided by 10 is 2 with a remainder of 4.
When I have a remainder, I need one additional page.
Set request attributes for the total products and total pages.
int totalProducts = ProductDAO.getProductCount(categories);
int totalPages = totalProducts / limit;
if(totalProducts % limit != 0) {
totalPages++;
}
req.setAttribute("totalProducts", totalProducts);
req.setAttribute("totalPages", totalPages);
Create a c:forEach tag for the pagination links.
In case their are 0 or 1 page of products to display, wrap everything inside a c:if statement to only show it if there is at least two pages.
As long as I have one page, I always begin on page 1. The ending page will be the value from the totalPages attribute. The variable "i" represents each count iteration.
The active class will only be applied to the current <li> tag that is selected using a <c:if> tag.
<c:if test="${totalPages > 1}">
<nav aria-label="...">
<!-- Code omitted -->
<c:forEach var="i" begin="1" end="${totalPages}">
<li class="page-item <c:if test="${page eq i}">active</c:if>">
<a class="page-link" href="${appURL}/shop?page=${i}">${i}</a>
</li>
</c:forEach>
<!-- Code omitted -->
</nav>
</c:if>
For the previous button, only activate it if we are not on page 1. When activated, make it go to the page before the current one.
For the next button, only activate it if we are not on the last page. When activated, make it go to the page after the current one.
<li class="page-item <c:if test="${page eq 1}">disabled</c:if>">
<a class="page-link" <c:if test="${page ne 1}">href="${appURL}/shop?page=${page - 1}"</c:if>>Previous</a>
</li>
<!-- Code omitted -->
<li class="page-item <c:if test="${page eq totalPages}">disabled</c:if>">
<a class="page-link" <c:if test="${page ne totalPages}">href="${appURL}/shop?page=${page + 1}"</c:if>>Next</a>
</li>
In the Shop servlet, determine which product is the first to be shown.
If we're on page 1, the first product shown is 1
1 + (1 - 1) * 10
1 - 1 = 0
0 * 10 = 0
1 + 0 = 1
If we're on page 2, the first product shown is 11
1 + (2 - 1) * 10
2 - 1 = 1
1 * 10 = 10
1 + 10 = 11
int firstProductShown = 1 + (page - 1) * limit;
req.setAttribute("firstProductShown", firstProductShown);
In the Shop servlet, determine which product is the last to be shown.
If we're on page 1, the last product shown is 10
10 + (1 - 1) * 10
1 - 1 = 0
0 * 10 = 0
10 + 0 = 10
If we're on page 3, the last product shown is 24
10 + (3 - 1) * 10
3 - 1 = 2
2 * 10 = 20
10 + 20 = 30
Because 30 is greater than 24, we set 24.
int lastProductShown = limit + (page - 1) * limit;
if(lastProductShown > totalProducts) {
lastProductShown = totalProducts;
}
req.setAttribute("lastProductShown", lastProductShown);
Write an if statement to make sure the page variable is within the range 1 to totalPages. (See lines 40-44)
I had to reorganize the code to get this to work.
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// Get limit
String limitStr = req.getParameter("limit");
int limit = 10;
try {
limit = Integer.parseInt(limitStr);
} catch (NumberFormatException e) {
if (limit < 0) {
limit = 10;
}
}
req.setAttribute("limit", limit);
// Get categories
String[] categoriesArr = req.getParameterValues("categories");
String categories = "";
if (categoriesArr != null && categoriesArr.length > 0) {
categories = String.join(",", categoriesArr);
}
req.setAttribute("categories", categories);
// Get total products and total pages
int totalProducts = ProductDAO.getProductCount(categories);
int totalPages = totalProducts / limit;
if(totalProducts % limit != 0) {
totalPages++;
}
req.setAttribute("totalProducts", totalProducts);
req.setAttribute("totalPages", totalPages);
// Get page
String pageStr = req.getParameter("page");
int page = 1;
try {
page = Integer.parseInt(pageStr);
} catch(NumberFormatException e) {
}
if(page < 1){
page = 1;
} else if(page > totalPages) {
page = totalPages;
}
req.setAttribute("page", page);
// Determine first and last product shown
int firstProductShown = 1 + (page - 1) * limit;
req.setAttribute("firstProductShown", firstProductShown);
int lastProductShown = limit + (page - 1) * limit;
if(lastProductShown > totalProducts) {
lastProductShown = totalProducts;
}
req.setAttribute("lastProductShown", lastProductShown);
// Calculate offset
int offset = (page - 1) * limit;
// Get products
List<Product> products = ProductDAO.getProducts(limit, offset, categories);
req.setAttribute("products", products);
// Get categories
List<ProductCategory> productCategories = ProductDAO.getAllCategories();
req.setAttribute("productCategories", productCategories);
req.getRequestDispatcher("WEB-INF/ecommerce/shop.jsp").forward(req, resp);
}
-- Bean Bag Toys (category_id = 1)
INSERT INTO Products (prod_id, vend_id, prod_name, prod_price, prod_desc, category_id)
VALUES
('BBT011', 'FRB01', 'Another Comfy Bean Bag Toy', 25.99, 'A soft and comfortable bean bag toy for kids.', 1),
('BBT012', 'FRB01', 'Another Fun Bean Bag Toy', 19.99, 'A playful and colorful bean bag toy for young children.', 1),
('BBT013', 'FRB01', 'Another Bean Bag Buddy', 29.99, 'A friendly and plush bean bag toy with a cute face.', 1),
('BBT014', 'FRB01', 'Another Squishy Bean Bag Toy', 24.99, 'A squishy and soft bean bag toy that hugs back.', 1),
('BBT015', 'FRB01', 'Another Plush Bean Bag Toy', 34.99, 'A large, plush bean bag toy perfect for cuddling.', 1),
('BBT021', 'FRB01', 'Yet Another Comfy Bean Bag Toy', 25.99, 'A soft and comfortable bean bag toy for kids.', 1),
('BBT022', 'FRB01', 'Yet Another Fun Bean Bag Toy', 19.99, 'A playful and colorful bean bag toy for young children.', 1),
('BBT023', 'FRB01', 'Yet Another Bean Bag Buddy', 29.99, 'A friendly and plush bean bag toy with a cute face.', 1),
('BBT024', 'FRB01', 'Yet Another Squishy Bean Bag Toy', 24.99, 'A squishy and soft bean bag toy that hugs back.', 1),
('BBT025', 'FRB01', 'Yet Another Plush Bean Bag Toy', 34.99, 'A large, plush bean bag toy perfect for cuddling.', 1),
('BBT031', 'FRB01', 'Still Another Comfy Bean Bag Toy', 25.99, 'A soft and comfortable bean bag toy for kids.', 1),
('BBT032', 'FRB01', 'Still Another Fun Bean Bag Toy', 19.99, 'A playful and colorful bean bag toy for young children.', 1),
('BBT033', 'FRB01', 'Still Another Bean Bag Buddy', 29.99, 'A friendly and plush bean bag toy with a cute face.', 1),
('BBT034', 'FRB01', 'Still Another Squishy Bean Bag Toy', 24.99, 'A squishy and soft bean bag toy that hugs back.', 1),
('BBT035', 'FRB01', 'Still Another Plush Bean Bag Toy', 34.99, 'A large, plush bean bag toy perfect for cuddling.', 1),
('BBT041', 'FRB01', 'Still Yet Another Comfy Bean Bag Toy', 25.99, 'A soft and comfortable bean bag toy for kids.', 1),
('BBT042', 'FRB01', 'Still Yet Another Fun Bean Bag Toy', 19.99, 'A playful and colorful bean bag toy for young children.', 1),
('BBT043', 'FRB01', 'Still Yet Another Bean Bag Buddy', 29.99, 'A friendly and plush bean bag toy with a cute face.', 1),
('BBT044', 'FRB01', 'Still Yet Another Squishy Bean Bag Toy', 24.99, 'A squishy and soft bean bag toy that hugs back.', 1),
('BBT045', 'FRB01', 'Still Yet Another 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
('TBE011', 'BRS01', 'Another Classic Teddy Bear', 40.00, 'A traditional teddy bear with soft fur and a red bow.', 2),
('TBE012', 'BRS01', 'Another Plush Teddy Bear', 45.50, 'A cuddly teddy bear with extra plush fur for comfort.', 2),
('TBE013', 'BRE02', 'Another Teddy Bear with Heart', 50.00, 'A teddy bear holding a heart-shaped pillow.', 2),
('TBE014', 'BRE02', 'Another Teddy Bear Family', 80.00, 'A set of three teddy bears of different sizes for family playtime.', 2),
('TBE015', 'BRS01', 'Another Sleepy Teddy Bear', 37.99, 'A sleepy teddy bear with a soft nightcap for bedtime comfort.', 2),
('TBE021', 'BRS01', 'Yet Another Classic Teddy Bear', 40.00, 'A traditional teddy bear with soft fur and a red bow.', 2),
('TBE022', 'BRS01', 'Yet Another Plush Teddy Bear', 45.50, 'A cuddly teddy bear with extra plush fur for comfort.', 2),
('TBE023', 'BRE02', 'Yet Another Teddy Bear with Heart', 50.00, 'A teddy bear holding a heart-shaped pillow.', 2),
('TBE024', 'BRE02', 'Yet Another Teddy Bear Family', 80.00, 'A set of three teddy bears of different sizes for family playtime.', 2),
('TBE025', 'BRS01', 'Yet Another Sleepy Teddy Bear', 37.99, 'A sleepy teddy bear with a soft nightcap for bedtime comfort.', 2),
('TBE031', 'BRS01', 'Still Another Classic Teddy Bear', 40.00, 'A traditional teddy bear with soft fur and a red bow.', 2),
('TBE032', 'BRS01', 'Still Another Plush Teddy Bear', 45.50, 'A cuddly teddy bear with extra plush fur for comfort.', 2),
('TBE033', 'BRE02', 'Still Another Teddy Bear with Heart', 50.00, 'A teddy bear holding a heart-shaped pillow.', 2),
('TBE034', 'BRE02', 'Still Another Teddy Bear Family', 80.00, 'A set of three teddy bears of different sizes for family playtime.', 2),
('TBE035', 'BRS01', 'Still Another Sleepy Teddy Bear', 37.99, 'A sleepy teddy bear with a soft nightcap for bedtime comfort.', 2),
('TBE041', 'BRS01', 'Still Yet Another Classic Teddy Bear', 40.00, 'A traditional teddy bear with soft fur and a red bow.', 2),
('TBE042', 'BRS01', 'Still Yet Another Plush Teddy Bear', 45.50, 'A cuddly teddy bear with extra plush fur for comfort.', 2),
('TBE043', 'BRE02', 'Still Yet Another Teddy Bear with Heart', 50.00, 'A teddy bear holding a heart-shaped pillow.', 2),
('TBE044', 'BRE02', 'Still Yet Another Teddy Bear Family', 80.00, 'A set of three teddy bears of different sizes for family playtime.', 2),
('TBE045', 'BRS01', 'Still Yet Another 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
('DOL011', 'DLL01', 'Another Classic Doll', 22.99, 'A timeless doll with long hair and a beautiful dress.', 3),
('DOL012', 'DLL01', 'Another Baby Doll', 19.99, 'A soft baby doll with a bottle and pacifier.', 3),
('DOL013', 'DLL01', 'Another Fashion Doll', 29.99, 'A stylish fashion doll with interchangeable outfits.', 3),
('DOL014', 'DLL01', 'Another Princess Doll', 35.50, 'A princess doll with a sparkling gown and tiara.', 3),
('DOL015', 'DLL01', 'Another Talking Doll', 44.99, 'An interactive doll that talks and sings songs.', 3),
('DOL021', 'DLL01', 'Yet Another Classic Doll', 22.99, 'A timeless doll with long hair and a beautiful dress.', 3),
('DOL022', 'DLL01', 'Yet Another Baby Doll', 19.99, 'A soft baby doll with a bottle and pacifier.', 3),
('DOL023', 'DLL01', 'Yet Another Fashion Doll', 29.99, 'A stylish fashion doll with interchangeable outfits.', 3),
('DOL024', 'DLL01', 'Yet Another Princess Doll', 35.50, 'A princess doll with a sparkling gown and tiara.', 3),
('DOL025', 'DLL01', 'Yet Another Talking Doll', 44.99, 'An interactive doll that talks and sings songs.', 3),
('DOL031', 'DLL01', 'Still Another Classic Doll', 22.99, 'A timeless doll with long hair and a beautiful dress.', 3),
('DOL032', 'DLL01', 'Still Another Baby Doll', 19.99, 'A soft baby doll with a bottle and pacifier.', 3),
('DOL033', 'DLL01', 'Still Another Fashion Doll', 29.99, 'A stylish fashion doll with interchangeable outfits.', 3),
('DOL034', 'DLL01', 'Still Another Princess Doll', 35.50, 'A princess doll with a sparkling gown and tiara.', 3),
('DOL035', 'DLL01', 'Still Another Talking Doll', 44.99, 'An interactive doll that talks and sings songs.', 3),
('DOL041', 'DLL01', 'Still Yet Another Classic Doll', 22.99, 'A timeless doll with long hair and a beautiful dress.', 3),
('DOL042', 'DLL01', 'Still Yet Another Baby Doll', 19.99, 'A soft baby doll with a bottle and pacifier.', 3),
('DOL043', 'DLL01', 'Still Yet Another Fashion Doll', 29.99, 'A stylish fashion doll with interchangeable outfits.', 3),
('DOL044', 'DLL01', 'Still Yet Another Princess Doll', 35.50, 'A princess doll with a sparkling gown and tiara.', 3),
('DOL045', 'DLL01', 'Still Yet Another Talking Doll', 44.99, 'An interactive doll that talks and sings songs.', 3);
// Calculate begin and end page links
int pageLinks = 5;
int beginPage = page / pageLinks * pageLinks > 0 ? page / pageLinks * pageLinks : 1;
int endPage = beginPage + pageLinks - 1 > totalPages ? totalPages : beginPage + pageLinks - 1;
req.setAttribute("beginPage", beginPage);
req.setAttribute("endPage", endPage);
<ul class="pagination">
<li class="page-item <c:if test="${page eq 1}">disabled</c:if>">
<a class="page-link" <c:if test="${page ne 1}">href="${appURL}/shop?page=1"</c:if>><i class="bi bi-chevron-double-left"></i></a>
</li>
<li class="page-item <c:if test="${page eq 1}">disabled</c:if>">
<a class="page-link" <c:if test="${page ne 1}">href="${appURL}/shop?page=${page - 1}"</c:if>><i class="bi bi-chevron-left"></i></a>
</li>
<c:forEach var="i" begin="${beginPage}" end="${endPage}">
<li class="page-item <c:if test="${page eq i}">active</c:if>">
<a class="page-link" href="${appURL}/shop?page=${i}">${i}</a>
</li>
</c:forEach>
<li class="page-item <c:if test="${page eq totalPages}">disabled</c:if>">
<a class="page-link" <c:if test="${page ne totalPages}">href="${appURL}/shop?page=${page + 1}"</c:if>><i class="bi bi-chevron-right"></i></a>
</li>
<li class="page-item <c:if test="${page eq totalPages}">disabled</c:if>">
<a class="page-link" <c:if test="${page ne totalPages}">href="${appURL}/shop?page=${totalPages}"</c:if>><i class="bi bi-chevron-double-right"></i></a>
</li>
</ul>
java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`my_database_name`.`password_reset`, CONSTRAINT `password_reset_ibfk_1` FOREIGN KEY (`email`) REFERENCES `user` (`email`) ON DELETE CASCADE)
DROP PROCEDURE sp_delete_password_reset;
CREATE PROCEDURE sp_delete_password_reset(IN p_email varchar(255))
BEGIN
DELETE FROM password_reset WHERE email = p_email;
END;
UserDAO.deletePasswordReset(user.getEmail());
public static void deletePasswordReset(String email) {
try (Connection connection = getConnection();
CallableStatement statement = connection.prepareCall("{CALL sp_delete_password_reset(?)}")) {
statement.setString(1, email);
statement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
// int id = resultSet.getInt("id");
// CallableStatement statement2 = connection.prepareCall("{CALL sp_delete_password_reset(?)}");
// statement2.setInt(1, id);
// statement2.executeUpdate();
deletePasswordReset(email);
Right now, the filtering and pagination don't work together. Combining them all should look something like this:
/shop?categories=1&categories=3&limit=5&page=2
In the Shop servlet, set the String[] categoriesArr as a request attribute.
req.setAttribute("categoriesArr", categoriesArr);
Two JSTL core tags we haven't discussed are c:url
and c:param
.
The c:url
tag is used to specify the servlet path.
The c:param
tags are used to define the parameters.
Because categoriesArr is an array of Strings, we need a c:forEach to create all of the parameters.
Find the c:forEach that is generating the pagination buttons. Replace the <a> tag on line 14 with this:
<a class="page-link" href="
<c:url value="${appURL}/shop">
<c:param name="page" value="${i}" />
<c:forEach items="${categoriesArr}" var="category">
<c:param name="categories" value="${category}" />
</c:forEach>
<c:if test="${not empty limit}"><c:param name="limit" value="${limit}" /></c:if>
</c:url>
">${i}</a>
Restart the server. Make category selection(s) and a limit selection. Click on the numbered pagination links.
Update the Beginning button "a" tag hyperlink to this. Note that only line 3 changed. Don't delete the icon.
<a class="page-link" href="
<c:url value="${appURL}/shop">
<c:param name="page" value="1" />
<c:forEach items="${categoriesArr}" var="category">
<c:param name="categories" value="${category}" />
</c:forEach>
<c:if test="${not empty limit}"><c:param name="limit" value="${limit}" /></c:if>
</c:url>
">
Update the Previous button "a" tag hyperlink to this. Note that only line 3 changed. Don't delete the icon.
<a class="page-link" href="
<c:url value="${appURL}/shop">
<c:param name="page" value="${page - 1}" />
<c:forEach items="${categoriesArr}" var="category">
<c:param name="categories" value="${category}" />
</c:forEach>
<c:if test="${not empty limit}"><c:param name="limit" value="${limit}" /></c:if>
</c:url>
">
Update the Next button "a" tag hyperlink to this. Note that only line 3 changed. Don't delete the icon.
<a class="page-link" href="
<c:url value="${appURL}/shop">
<c:param name="page" value="${page + 1}" />
<c:forEach items="${categoriesArr}" var="category">
<c:param name="categories" value="${category}" />
</c:forEach>
<c:if test="${not empty limit}"><c:param name="limit" value="${limit}" /></c:if>
</c:url>
">
Update the Last button "a" tag hyperlink to this. Note that only line 3 changed. Don't delete the icon.
<a class="page-link" href="
<c:url value="${appURL}/shop">
<c:param name="page" value="${totalPages}" />
<c:forEach items="${categoriesArr}" var="category">
<c:param name="categories" value="${category}" />
</c:forEach>
<c:if test="${not empty limit}"><c:param name="limit" value="${limit}" /></c:if>
</c:url>
">
Inside the form element in the shop-sidebar, add a hidden attribute to add the current page to the form's GET request.
Now when you submit the form, the page number will be part of the request.
<form method="GET" action="${appURL}/shop">
<input type="hidden" name="page" value="${page}">
In the Shop servlet, note that we are setting two attributes that are very similar.
req.setAttribute("categories", categories);
req.setAttribute("categoriesArr", categoriesArr);
An example of "categories" is a String, like "1", "1,3", etc.
An example of "categoriesArr" is a String[] like ["1"], ["1","3"], etc.
Do we need to set both attributes? We need the categoriesArr for the pagination hyperlinks. Can we eliminate the categories string?
Inside the c:forEach loop that generates the checkboxes, there is a c:if tag that uses a fn:contains() function to add a checked attribute if the categories String contains a matching value.
<c:if test="${fn:contains(categories, category.id)}">checked</c:if>
If you change categories to categoriesArr, the checks don't work correctly
To solve this, we must write a custom tag library descriptor (TLD).
A tag library descriptor is an XML document that contains information about a tag library and each function or tag included in the library.
In fairness, you may not have to create custom tags very often; the standard tags provide so much functionality. However, knowing that you can do it is excellent when you need customization.
Inspect the TLD files for these libraries. Notice the common tags. Compare the tld files with the documentation.
In the WEB-INF folder, create a "tld" directory. Inside that folder create a file called "custom-functions.tld". Add this code.
Read more about this code on the next slide.
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-jsptaglibrary_3_0.xsd"
version="3.0">
<tlib-version>1.0</tlib-version>
<short-name>custom</short-name>
<uri>/WEB-INF/tld</uri>
</taglib>
This code sets up the document root element <taglib> and declares that the document uses the XML schema definition.
Inside the <taglib> tags come the following tags:
<tlib-version> defines the version of your tag library and may only contain numbers and periods. This tag is required.
<short-name> indicates this tag library's preferred and default prefix (or namespace). This tag is required.
<uri> defines the URI for this tag library. It doesn't have to be a URL to an actual resource.
Optional <description> and <display-name> elements provide useful names that can be displayed
Create a Functions class in the "shared" package.
Add a method that takes in an array of Strings and an int and returns true if the array contains the item.
There is a possibility that the array might be null. In that case we return false.
import java.util.Arrays;
public class Functions {
public static boolean contains(String[] array, int item) {
if(array == null) {
return false;
}
return Arrays.asList(array).contains(String.valueOf(item));
}
}
Add these tags between the taglib tags of the custom-functions.tld file. Replace "edu.kirkwood.shared" with the path of your Functions class.
<function>
<description>
Checks an array of category id Strings contains a category id (int)
</description>
<name>contains</name>
<function-class>edu.kirkwood.shared.Functions</function-class>
<function-signature>boolean contains(java.lang.String[], int)</function-signature>
</function>
Add this custom taglib directive to the top.jspf file.
<%@ taglib prefix="cfn" uri="/WEB-INF/tld/custom-functions.tld" %>
<c:if test="${cfn:contains(categoriesArr, category.id)}">checked</c:if>
Add sort and search
Add phone number custom format (See week 13)
Add text abbreviator... custom format
By Marc Hauschildt
Web Technologies and Computer Software Development Instructor at Kirkwood Community College in Cedar Rapids, IA.