Tutorial 7: Pagination & UI Components¶
Pages: 2
๐ Page 1: Pagination Logic¶
Add Pagination to Service¶
Add to PatientService.java:
public Map<String, Object> getPaginatedPatients(
List<PatientDTO> allPatients, int page, int size) {
int totalItems = allPatients.size();
int totalPages = (int) Math.ceil((double) totalItems / size);
// Validate page number
if (page < 1) page = 1;
if (page > totalPages && totalPages > 0) page = totalPages;
// Calculate start and end index
int startIndex = (page - 1) * size;
int endIndex = Math.min(startIndex + size, totalItems);
// Get page items
List<PatientDTO> pageItems = startIndex < totalItems
? allPatients.subList(startIndex, endIndex)
: new ArrayList<>();
// Calculate page numbers to display (current ยฑ 2)
int startPage = Math.max(1, page - 2);
int endPage = Math.min(totalPages, page + 2);
// Build result map
Map<String, Object> result = new HashMap<>();
result.put("items", pageItems);
result.put("currentPage", page);
result.put("pageSize", size);
result.put("totalItems", totalItems);
result.put("totalPages", totalPages);
result.put("startPage", startPage);
result.put("endPage", endPage);
result.put("startIndex", startIndex);
result.put("endIndex", endIndex);
return result;
}
Update Controller¶
Update listPatients in PatientController:
@GetMapping
public String listPatients(
// ... existing filter params ...
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
Model model) {
// Filter and sort (as before)
List<PatientDTO> patients = patientService.filterPatients(
name, gender, bloodGroup, status, minAge, maxAge
);
patients = patientService.sortPatients(patients, sortBy, order);
// Paginate
Map<String, Object> pageData = patientService.getPaginatedPatients(
patients, page, size
);
// Add all to model
model.addAllAttributes(pageData);
model.addAttribute("name", name);
model.addAttribute("gender", gender);
// ... add all filter params
return "patients/list";
}
๐ Page 2: Pagination UI¶
Add Pagination Controls to list.html¶
Add after the table in templates/patients/list.html:
<!-- Pagination Info -->
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
Showing
<span th:text="${startIndex + 1}"></span> to
<span th:text="${endIndex}"></span> of
<span th:text="${totalItems}"></span> entries
</div>
<!-- Page Size Selector -->
<div>
<span>Show:</span>
<a th:href="@{/patients(name=${name},gender=${gender},page=1,size=10)}"
th:classappend="${pageSize == 10 ? 'fw-bold' : ''}"
class="btn btn-sm btn-outline-secondary">10</a>
<a th:href="@{/patients(name=${name},gender=${gender},page=1,size=25)}"
th:classappend="${pageSize == 25 ? 'fw-bold' : ''}"
class="btn btn-sm btn-outline-secondary">25</a>
<a th:href="@{/patients(name=${name},gender=${gender},page=1,size=50)}"
th:classappend="${pageSize == 50 ? 'fw-bold' : ''}"
class="btn btn-sm btn-outline-secondary">50</a>
</div>
</div>
<!-- Pagination Controls -->
<nav th:if="${totalPages > 1}">
<ul class="pagination justify-content-center">
<!-- First Page -->
<li class="page-item" th:classappend="${currentPage == 1 ? 'disabled' : ''}">
<a class="page-link"
th:href="@{/patients(name=${name},gender=${gender},page=1,size=${pageSize})}"
>ยซ</a>
</li>
<!-- Previous -->
<li class="page-item" th:classappend="${currentPage == 1 ? 'disabled' : ''}">
<a class="page-link"
th:href="@{/patients(name=${name},gender=${gender},
page=${currentPage - 1},size=${pageSize})}"
>โน</a>
</li>
<!-- Page Numbers -->
<li th:each="i : ${#numbers.sequence(startPage, endPage)}"
class="page-item"
th:classappend="${i == currentPage ? 'active' : ''}">
<a class="page-link"
th:href="@{/patients(name=${name},gender=${gender},page=${i},size=${pageSize})}"
th:text="${i}"></a>
</li>
<!-- Next -->
<li class="page-item"
th:classappend="${currentPage == totalPages ? 'disabled' : ''}">
<a class="page-link"
th:href="@{/patients(name=${name},gender=${gender},
page=${currentPage + 1},size=${pageSize})}"
>โบ</a>
</li>
<!-- Last Page -->
<li class="page-item"
th:classappend="${currentPage == totalPages ? 'disabled' : ''}">
<a class="page-link"
th:href="@{/patients(name=${name},gender=${gender},
page=${totalPages},size=${pageSize})}"
>ยป</a>
</li>
</ul>
</nav>
Update Table with Serial Numbers¶
Update table in list.html:
<table class="table table-striped">
<thead>
<tr>
<th>#</th> <!-- Serial number across pages -->
<th>Name</th>
<th>Age</th>
<th>Gender</th>
<th>Email</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr th:each="patient, stat : ${items}">
<!-- Serial number: (page-1)*size + position -->
<td th:text="${(currentPage - 1) * pageSize + stat.count}"></td>
<td>
<a th:href="@{/patients/{id}(id=${patient.id})}"
th:text="${patient.fullName}"></a>
</td>
<td th:text="${T(java.time.LocalDate).now().getYear() -
patient.birthDate.getYear()}"></td>
<td th:text="${patient.gender}"></td>
<td th:text="${patient.email}"></td>
<td>
<a th:href="@{/patients/{id}/edit(id=${patient.id})}"
class="btn btn-sm btn-warning">Edit</a>
<form th:action="@{/patients/{id}/delete(id=${patient.id})}"
method="post" style="display:inline">
<button class="btn btn-sm btn-danger"
onclick="return confirm('Delete?')">ร</button>
</form>
</td>
</tr>
</tbody>
</table>
โ Pagination Complete¶
- Page calculation (totalPages, startIndex, endIndex)
- Page numbers (current ยฑ 2 pages shown)
- First/Last/Prev/Next buttons
- Page size selector (10/25/50)
- Entry counter (showing X to Y of Z)
- Serial numbers continue across pages
- Filters preserved in pagination
๐ Next¶
Patient module complete! Now build other modules.
๐ก Quick Tips¶
Serial Numbers
Formula: (currentPage - 1) * pageSize + position
Page Range
Show currentPage ยฑ 2 for clean pagination (5 pages max)
Preserve Filters
Always pass filter params in pagination links