Skip to content

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.

โ†’ Tutorial 8: 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