Skip to content

Commit 4704ed2

Browse files
committed
Add paging functionality on overview page
1 parent 008c5cb commit 4704ed2

9 files changed

Lines changed: 372 additions & 262 deletions

File tree

README.md

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,4 +397,85 @@ Follow these steps to implement the bookshop:
397397
398398
```
399399
7. The card page should be fully functional, now, and look similar to this:<br/>
400-
![cart page](src/main/resources/screenshots/cart-page.png)
400+
![cart page](src/main/resources/screenshots/cart-page.png)
401+
402+
## 7. Handle pagination on the overview page
403+
Now, we can see all 209 books from the [CSV file](src/main/resources/books.csv) but these are too many entries at once. To handle this, we introduce a `pagination`, i.e. show only 15 books at once and offer a navigation to the next / previous page.
404+
405+
### Control pagination via request parameter
406+
1. We want to control the pagination via the URL parameter `page`. So we use a request parameter in our `homePage` method.
407+
```java
408+
@GetMapping(value = {"/index.html"})
409+
public String homePage(Model model, @RequestParam(name = "page", required = false) Integer page)
410+
```
411+
2. We want to memorize the selected page when the user returns from another page, so we store it in the session. If there is a `page` request parameter at the same time, then it should be used. If none of both is present, use a default value. We handle this logic in the `getSessionParam()` method:
412+
```java
413+
Integer getSessionParam(HttpSession session, String paramName, Integer paramValue, Integer defaultValue)
414+
```
415+
3. Next, we create a method that handles the pagination of the articles array:
416+
```java
417+
handlePagination(Model model, Integer page)
418+
```
419+
### Display 'from', 'to' and total number of articles
420+
1. We need to calculate the `from` and `to` index to be able to return the proper sublist of articles.
421+
```java
422+
int numOfArticles = shop.getNumOfArticles();
423+
int from = Math.max((page - 1) * PAGE_SIZE, 0);
424+
int to = Math.min(numOfArticles, from + PAGE_SIZE);
425+
List<Book> articles = shop.getArticles().subList(from, to);
426+
```
427+
2. We add all this attributes to the view model:
428+
```java
429+
model.addAttribute("from", ++from);
430+
model.addAttribute("to", to);
431+
model.addAttribute("numOfArticles", numOfArticles);
432+
model.addAttribute("articles", articles);
433+
```
434+
3. In [index.html](src/main/resources/templates/index.html), scroll to the `showing-product` section and insert this Mustache code:
435+
```handlebars
436+
<div class="showing-product">
437+
<p>Showing {{from}}-{{to}} of {{numOfArticles}} results</p>
438+
</div>
439+
```
440+
4. Test it by using different `page` parameters in your browser, e.g. http://localhost:8080/index.html?page=2. It should display the proper from/to values:<br/>
441+
![Proper from-to display](src/main/resources/screenshots/from-to-display.png)
442+
443+
### Create pagination links
444+
As Mustache doesn't handle logic, we have to implement it in the controller.
445+
1. To do so, we will use a Map with the `pageNumber` as key and the current page's `active` state as value.
446+
```java
447+
int pageCount = (numOfArticles / PAGE_SIZE) + 1;
448+
Map<Integer, String> pages = new HashMap<>();
449+
for(int pageNumber = 1; pageNumber <= pageCount; pageNumber++) {
450+
String active = (pageNumber == page) ? "active" : "";
451+
pages.put(pageNumber, active);
452+
}
453+
```
454+
2. We also need page values for the `previous` and `next` page links. We add all this attributes to the view model:
455+
```java
456+
model.addAttribute("pageCount", pageCount);
457+
model.addAttribute("pages", pages.entrySet());
458+
model.addAttribute("prevPage", Math.max(page - 1, 1));
459+
model.addAttribute("nextPage", Math.min(page + 1, pageCount));
460+
```
461+
3. To show the pagination links, scroll to the `Page navigation` section in [pagination.html](src/main/resources/templates/partials/pagination.html) and insert this code:
462+
```handlebars
463+
<nav class="py-5" aria-label="Page navigation">
464+
<ul class="pagination justify-content-center gap-4">
465+
<li class="page-item">
466+
<a class="page-link" href="?page={{prevPage}}">&lt;</a>
467+
</li>
468+
{{#pages}}
469+
<li class="page-item">
470+
<a class="page-link {{value}}" href="?page={{key}}">{{key}}</a>
471+
</li>
472+
{{/pages}}
473+
<li class="page-item">
474+
<a class="page-link" href="?page={{nextPage}}">&gt;</a>
475+
</li>
476+
</ul>
477+
</nav>
478+
```
479+
4. The result should look like this:<br/>
480+
![Pagination screenshot](src/main/resources/screenshots/pagination.png)
481+
5. Test the proper pagination by clicking the links!

src/main/java/onlineshop/Shop.java

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,12 @@
2222
@SpringBootApplication
2323
public class Shop {
2424
public static final DecimalFormat df = new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.US));
25-
26-
private final static String RESOURCES = "src/main/resources/";
27-
private final static String CSV_FILE = "books.csv";
28-
private static Logger log = LogManager.getLogger(Shop.class);
25+
private final static Logger log = LogManager.getLogger(Shop.class);
2926
private final static List<Book> books = new ArrayList<>(220);
3027

31-
3228
public static void main(String[] args) {
3329
Shop shop = new Shop();
34-
shop.readArticles(CSV_FILE, books);
30+
shop.readArticles("src/main/resources/books.csv", books);
3531
SpringApplication.run(Shop.class, args);
3632
log.info("Server started on http://localhost:8080");
3733
}
@@ -51,29 +47,27 @@ public int getNumOfArticles() {
5147
*/
5248
private void readArticles(String fileName, List<Book> books) {
5349
try {
54-
Reader in = new FileReader(RESOURCES + fileName);
50+
Reader in = new FileReader(fileName);
5551
CSVFormat csvFormat = CSVFormat.EXCEL.withFirstRecordAsHeader().builder()
5652
.setDelimiter(';')
5753
.build();
5854
Iterable<CSVRecord> records = csvFormat.parse(in);
5955

6056
for (CSVRecord record : records) {
61-
String title = record.get("Title");
62-
String author = record.get("Author");
63-
String publisher = record.get("Publisher");
64-
String genre = record.get("Genre");
65-
int pages = Integer.parseInt(record.get("Pages"));
57+
String title = record.get("title");
58+
String author = record.get("author");
59+
String publisher = record.get("publisher");
60+
String genre = record.get("genre");
6661

6762
double price = 0.0;
68-
String priceString = record.get("Price");
63+
String priceString = record.get("price");
6964
if (!priceString.isEmpty()) {
70-
priceString = priceString.replace(',', '.');
7165
price = Double.parseDouble(priceString);
7266
}
7367

74-
String image = record.get("Image");
68+
String image = record.get("image");
7569
if (image.isEmpty()) image = "/images/book-placeholder.png";
76-
Book book = new Book(title, author, publisher, genre, pages, price, image);
70+
Book book = new Book(title, author, publisher, genre, price, image);
7771
books.add(book);
7872
}
7973
in.close();
@@ -85,12 +79,13 @@ private void readArticles(String fileName, List<Book> books) {
8579

8680
/**
8781
* Gets a /book by its article number
82+
*
8883
* @param articleNo {@link Integer}
8984
* @return existingBook {@link Book}
9085
*/
9186
public Book getArticleByNumber(int articleNo) {
9287
for (Book book : books) {
93-
if(book.getArticleNo() == articleNo)
88+
if (book.getArticleNo() == articleNo)
9489
return book;
9590
}
9691
return null;

src/main/java/onlineshop/controllers/ShopController.java

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,23 @@
55
import onlineshop.Shop;
66
import onlineshop.merchandise.Book;
77
import onlineshop.merchandise.CartItem;
8+
import org.apache.logging.log4j.LogManager;
9+
import org.apache.logging.log4j.Logger;
810
import org.springframework.beans.factory.annotation.Autowired;
911
import org.springframework.stereotype.Controller;
1012
import org.springframework.ui.Model;
1113
import org.springframework.web.bind.annotation.GetMapping;
1214
import org.springframework.web.bind.annotation.PathVariable;
1315
import org.springframework.web.bind.annotation.RequestParam;
1416

17+
import java.util.HashMap;
1518
import java.util.List;
19+
import java.util.Map;
1620

1721
@Controller
1822
public class ShopController {
19-
public static final String PAGE_NO = "page";
20-
public static final String PAGE_SIZE = "size";
21-
public final static String ARTICLES = "articles";
23+
private final static Logger log = LogManager.getLogger(ShopController.class);
24+
public static final int PAGE_SIZE = 15;
2225

2326
@Autowired
2427
Shop shop;
@@ -34,43 +37,77 @@ public String root() {
3437
@GetMapping(value = {"/index.html"})
3538
public String homePage(Model model,
3639
@RequestParam(name = "page", required = false) Integer page,
37-
@RequestParam(name = "size", required = false) Integer size,
3840
HttpSession session) {
39-
page = getSessionParam(session, PAGE_NO, page, 0);
40-
size = getSessionParam(session, PAGE_SIZE, size, 10);
41-
42-
int from = page * size;
43-
int to = from + size;
44-
List<Book> articles = shop.getArticles().subList(from, to);
45-
46-
model.addAttribute(ARTICLES, articles);
47-
model.addAttribute("from", from);
48-
model.addAttribute("to", to);
49-
model.addAttribute("noOfArticles", shop.getNumOfArticles());
41+
page = getSessionParam(session, "page", page, 1);
42+
handlePagination(model, page);
5043
getCartItems(model);
5144
return "index";
5245
}
5346

5447
@GetMapping(value = {"/{name}.html"})
55-
public String htmlMapping(Model model,
56-
@PathVariable(name = "name") String name) {
48+
public String htmlMapping(Model model, @PathVariable(name = "name") String name) {
5749
getCartItems(model);
5850
return name;
5951
}
6052

53+
/**
54+
* Loads the cart items from the cart object and stores the corresponding attributes in the view model.
55+
* @param model {@link Model}
56+
*/
6157
private void getCartItems(Model model) {
6258
List<CartItem> cartItems = cart.getItems();
6359
model.addAttribute("cartItems", cartItems);
6460
model.addAttribute("numOfCartItems", cart.getNumOfItems());
6561
model.addAttribute("grandTotal", cart.getGrandTotal());
6662
}
6763

68-
private Integer getSessionParam(HttpSession session, String paramName, Integer paramValue, Integer defaultValue) {
64+
/**
65+
* Looks up the requested parameter in the session. If it doesn't exist, it uses the default value.
66+
* @param session {@link jakarta.servlet.http.HttpSession}
67+
* @param paramName {@link String}
68+
* @param paramValue {@link Integer}
69+
* @param defaultValue {@link Integer}
70+
* @return sessionValue {@link Integer}
71+
*/
72+
private Integer getSessionParam(HttpSession session,
73+
String paramName,
74+
Integer paramValue,
75+
Integer defaultValue) {
6976
if (paramValue == null) {
7077
Integer sessionValue = (Integer) session.getAttribute(paramName);
7178
paramValue = sessionValue == null ? defaultValue : sessionValue;
7279
}
7380
session.setAttribute(paramName, paramValue);
7481
return paramValue;
7582
}
83+
84+
/**
85+
* Delivers the articles sublist corresponding to the selected page
86+
* @param model {@link Model}
87+
* @param page {@link Integer}
88+
*/
89+
private void handlePagination(Model model, Integer page) {
90+
int numOfArticles = shop.getNumOfArticles();
91+
int from = Math.max((page - 1) * PAGE_SIZE, 0);
92+
int to = Math.min(numOfArticles, from + PAGE_SIZE);
93+
List<Book> articles = shop.getArticles().subList(from, to);
94+
95+
model.addAttribute("articles", articles);
96+
model.addAttribute("from", ++from);
97+
model.addAttribute("to", to);
98+
model.addAttribute("numOfArticles", numOfArticles);
99+
100+
int pageCount = (numOfArticles / PAGE_SIZE) + 1;
101+
Map<Integer, String> pages = new HashMap<>();
102+
for(int pageNumber = 1; pageNumber <= pageCount; pageNumber++) {
103+
String active = (pageNumber == page) ? "active" : "";
104+
pages.put(pageNumber, active);
105+
}
106+
107+
model.addAttribute("pageCount", pageCount);
108+
model.addAttribute("pages", pages.entrySet());
109+
model.addAttribute("prevPage", Math.max(page - 1, 1));
110+
model.addAttribute("nextPage", Math.min(page + 1, pageCount));
111+
}
112+
76113
}

src/main/java/onlineshop/merchandise/Book.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@ public Book(String title, String author) {
1919
this.author = author;
2020
}
2121

22-
public Book(String title, String author, String publisher, String genre, int pages, double price, String image) {
22+
public Book(String title, String author, String publisher, String genre, double price, String image) {
2323
super("Description will follow...", publisher, price, image);
2424
this.title = title;
2525
this.author = author;
26-
this.pages = pages;
2726
this.format = Format.PAPERBACK;
2827
this.genre = Genre.fromString(genre);
2928
}

0 commit comments

Comments
 (0)