├── .gitignore
├── README.md
├── pom.xml
└── src
├── main
├── java
│ └── org
│ │ └── paginationdemo
│ │ ├── Application.java
│ │ ├── controller
│ │ └── PersonController.java
│ │ ├── domain
│ │ ├── Pager.java
│ │ └── Person.java
│ │ ├── repository
│ │ └── PersonRepository.java
│ │ └── service
│ │ ├── PersonService.java
│ │ └── impl
│ │ └── PersonServiceImpl.java
└── resources
│ ├── application.properties
│ ├── data.sql
│ ├── static
│ ├── css
│ │ └── style.css
│ └── js
│ │ └── app.js
│ └── templates
│ └── persons.html
└── test
└── java
└── org
└── paginationdemo
└── ApplicationTests.java
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | .classpath
3 | .project
4 | .settings/
5 | /.idea/
6 | *.iml
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Demo for pagination with Thymeleaf template engine
2 |
3 | ## How to run
4 |
5 | Run `mvn install` in root directory
6 |
7 | Run `java -jar target/spring-thymeleaf-pagination-0.0.1-SNAPSHOT.jar`
8 |
9 | Go to http://localhost:8080
10 |
11 | Special thanks to [Bruno Raljic](https://github.com/brunoraljic) for implementation of algorithm that calculates button span.
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.paginationdemo
7 | spring-thymeleaf-pagination
8 | 0.0.1-SNAPSHOT
9 | jar
10 |
11 | spring-thymeleaf-pagination
12 | Thymeleaf pagination demo with Spring
13 |
14 |
15 | org.springframework.boot
16 | spring-boot-starter-parent
17 | 2.3.1.RELEASE
18 |
19 |
20 |
21 |
22 | UTF-8
23 | 15
24 |
25 |
26 |
27 |
28 | org.springframework.boot
29 | spring-boot-starter-data-jpa
30 |
31 |
32 |
33 | org.springframework.boot
34 | spring-boot-starter-thymeleaf
35 |
36 |
37 | org.springframework.boot
38 | spring-boot-starter-web
39 |
40 |
41 | org.webjars
42 | bootstrap
43 | 3.3.7-1
44 |
45 |
46 |
47 | com.h2database
48 | h2
49 | runtime
50 |
51 |
52 | org.springframework.boot
53 | spring-boot-starter-test
54 | test
55 |
56 |
57 |
58 |
59 |
60 |
61 | org.springframework.boot
62 | spring-boot-maven-plugin
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/src/main/java/org/paginationdemo/Application.java:
--------------------------------------------------------------------------------
1 | package org.paginationdemo;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class Application {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(Application.class, args);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/org/paginationdemo/controller/PersonController.java:
--------------------------------------------------------------------------------
1 | package org.paginationdemo.controller;
2 |
3 | import org.paginationdemo.domain.Pager;
4 | import org.paginationdemo.service.PersonService;
5 | import org.springframework.data.domain.PageRequest;
6 | import org.springframework.stereotype.Controller;
7 | import org.springframework.web.bind.annotation.GetMapping;
8 | import org.springframework.web.bind.annotation.RequestParam;
9 | import org.springframework.web.servlet.ModelAndView;
10 |
11 | import java.util.Optional;
12 |
13 | /**
14 | * @author Branislav Lazic
15 | */
16 | @Controller
17 | public class PersonController {
18 |
19 | private static final int BUTTONS_TO_SHOW = 5;
20 | private static final int INITIAL_PAGE = 0;
21 | private static final int INITIAL_PAGE_SIZE = 5;
22 | private static final int[] PAGE_SIZES = {5, 10, 20};
23 |
24 | private final PersonService personService;
25 |
26 | public PersonController(PersonService studentService) {
27 | this.personService = studentService;
28 | }
29 |
30 | /**
31 | * Handles all requests
32 | *
33 | * @param pageSize - the size of the page
34 | * @param page - the page number
35 | * @return model and view
36 | */
37 | @GetMapping("/")
38 | public ModelAndView showPersonsPage(@RequestParam("pageSize") Optional pageSize,
39 | @RequestParam("page") Optional page) {
40 | var modelAndView = new ModelAndView("persons");
41 |
42 | // Evaluate page size. If requested parameter is null, return initial
43 | // page size
44 | int evalPageSize = pageSize.orElse(INITIAL_PAGE_SIZE);
45 | // If a requested parameter is null or less than 1,
46 | // return the initial size. Otherwise, return value of
47 | // param. decreased by 1.
48 | int evalPage = page.filter(p -> p >= 1)
49 | .map(p -> p - 1)
50 | .orElse(INITIAL_PAGE);
51 |
52 | var persons = personService.findAllPageable(PageRequest.of(evalPage, evalPageSize));
53 | var pager = new Pager(persons.getTotalPages(), persons.getNumber(), BUTTONS_TO_SHOW);
54 |
55 | modelAndView.addObject("persons", persons);
56 | modelAndView.addObject("selectedPageSize", evalPageSize);
57 | modelAndView.addObject("pageSizes", PAGE_SIZES);
58 | modelAndView.addObject("pager", pager);
59 | return modelAndView;
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/org/paginationdemo/domain/Pager.java:
--------------------------------------------------------------------------------
1 | package org.paginationdemo.domain;
2 |
3 | /**
4 | * Used to calculate span of buttons which will be displayed on a page
5 | *
6 | * @author Branislav Lazic
7 | * @author Bruno Raljic
8 | */
9 | public class Pager {
10 |
11 | private int buttonsToShow = 5;
12 |
13 | private int startPage;
14 |
15 | private int endPage;
16 |
17 | public Pager(int totalPages, int currentPage, int buttonsToShow) {
18 |
19 | setButtonsToShow(buttonsToShow);
20 |
21 | int halfPagesToShow = getButtonsToShow() / 2;
22 |
23 | if (totalPages <= getButtonsToShow()) {
24 | setStartPage(1);
25 | setEndPage(totalPages);
26 |
27 | } else if (currentPage - halfPagesToShow <= 0) {
28 | setStartPage(1);
29 | setEndPage(getButtonsToShow());
30 |
31 | } else if (currentPage + halfPagesToShow == totalPages) {
32 | setStartPage(currentPage - halfPagesToShow);
33 | setEndPage(totalPages);
34 |
35 | } else if (currentPage + halfPagesToShow > totalPages) {
36 | setStartPage(totalPages - getButtonsToShow() + 1);
37 | setEndPage(totalPages);
38 |
39 | } else {
40 | setStartPage(currentPage - halfPagesToShow);
41 | setEndPage(currentPage + halfPagesToShow);
42 | }
43 |
44 | }
45 |
46 | public int getButtonsToShow() {
47 | return buttonsToShow;
48 | }
49 |
50 | public void setButtonsToShow(int buttonsToShow) {
51 | if (buttonsToShow % 2 != 0) {
52 | this.buttonsToShow = buttonsToShow;
53 | } else {
54 | throw new IllegalArgumentException("Must be an odd value!");
55 | }
56 | }
57 |
58 | public int getStartPage() {
59 | return startPage;
60 | }
61 |
62 | public void setStartPage(int startPage) {
63 | this.startPage = startPage;
64 | }
65 |
66 | public int getEndPage() {
67 | return endPage;
68 | }
69 |
70 | public void setEndPage(int endPage) {
71 | this.endPage = endPage;
72 | }
73 |
74 | @Override
75 | public String toString() {
76 | return "Pager [startPage=" + startPage + ", endPage=" + endPage + "]";
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/org/paginationdemo/domain/Person.java:
--------------------------------------------------------------------------------
1 | package org.paginationdemo.domain;
2 |
3 | import javax.persistence.Entity;
4 | import javax.persistence.GeneratedValue;
5 | import javax.persistence.GenerationType;
6 | import javax.persistence.Id;
7 |
8 | @Entity
9 | public class Person {
10 |
11 | @Id
12 | @GeneratedValue(strategy = GenerationType.IDENTITY)
13 | private long personId;
14 |
15 | private String firstName;
16 |
17 | private String lastName;
18 |
19 | private int age;
20 |
21 | public Person() {
22 | }
23 |
24 | public Person(String firstName, String lastName, int age) {
25 | this.firstName = firstName;
26 | this.lastName = lastName;
27 | this.age = age;
28 | }
29 |
30 | public long getPersonId() {
31 | return personId;
32 | }
33 |
34 | public void setPersonId(long studentId) {
35 | this.personId = studentId;
36 | }
37 |
38 | public String getFirstName() {
39 | return firstName;
40 | }
41 |
42 | public void setFirstName(String firstName) {
43 | this.firstName = firstName;
44 | }
45 |
46 | public String getLastName() {
47 | return lastName;
48 | }
49 |
50 | public void setLastName(String lastName) {
51 | this.lastName = lastName;
52 | }
53 |
54 | public int getAge() {
55 | return age;
56 | }
57 |
58 | public void setAge(int age) {
59 | this.age = age;
60 | }
61 |
62 | @Override
63 | public String toString() {
64 | return "Person [personId=" + personId + ", firstName=" + firstName + ", lastName=" + lastName + ", age=" + age + "]";
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/org/paginationdemo/repository/PersonRepository.java:
--------------------------------------------------------------------------------
1 | package org.paginationdemo.repository;
2 |
3 | import org.paginationdemo.domain.Person;
4 | import org.springframework.data.repository.PagingAndSortingRepository;
5 | import org.springframework.stereotype.Repository;
6 |
7 | @Repository
8 | public interface PersonRepository extends PagingAndSortingRepository {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/org/paginationdemo/service/PersonService.java:
--------------------------------------------------------------------------------
1 | package org.paginationdemo.service;
2 |
3 | import org.paginationdemo.domain.Person;
4 | import org.springframework.data.domain.Page;
5 | import org.springframework.data.domain.Pageable;
6 |
7 | public interface PersonService {
8 |
9 | /**
10 | * Finds a "page" of persons
11 | *
12 | * @param pageable
13 | * @return {@link Page} instance
14 | */
15 | Page findAllPageable(Pageable pageable);
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/org/paginationdemo/service/impl/PersonServiceImpl.java:
--------------------------------------------------------------------------------
1 | package org.paginationdemo.service.impl;
2 |
3 | import org.paginationdemo.domain.Person;
4 | import org.paginationdemo.repository.PersonRepository;
5 | import org.paginationdemo.service.PersonService;
6 | import org.springframework.data.domain.Page;
7 | import org.springframework.data.domain.Pageable;
8 | import org.springframework.stereotype.Service;
9 |
10 | @Service
11 | public class PersonServiceImpl implements PersonService {
12 |
13 | private final PersonRepository personRepository;
14 |
15 | public PersonServiceImpl(PersonRepository personRepository) {
16 | this.personRepository = personRepository;
17 | }
18 |
19 | @Override
20 | public Page findAllPageable(Pageable pageable) {
21 | return personRepository.findAll(pageable);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.thymeleaf.cache=false
--------------------------------------------------------------------------------
/src/main/resources/data.sql:
--------------------------------------------------------------------------------
1 | -- test data generated by http://www.generatedata.com/
2 | insert into person
3 | (first_name, last_name, age)
4 | values
5 | ('Aiko', 'Thompson', 23),
6 | ('Dorian', 'Carpenter', 61),
7 | ('Blythe', 'Harvey', 69),
8 | ('Lunea', 'Mcconnell', 17),
9 | ('Hector', 'Hopkins', 23),
10 | ('Darius', 'Brady', 54),
11 | ('Samuel', 'Kennedy', 88),
12 | ('Allegra', 'Anderson', 32),
13 | ('Mollie', 'Logan', 43),
14 | ('Belle', 'Francis', 65),
15 | ('Ivan', 'Nguyen', 55),
16 | ('Alden', 'Christensen', 22),
17 | ('Selma', 'Hendrix', 81),
18 | ('Pascale', 'Padilla', 44),
19 | ('Brody', 'Rosales', 44),
20 | ('Tucker', 'Simmons', 95),
21 | ('Zelenia', 'Slater', 63),
22 | ('Skyler', 'Bush', 17),
23 | ('Zephr', 'Alexander', 52),
24 | ('Myra', 'Mccarthy', 53),
25 | ('Ross', 'Hebert', 88),
26 | ('Craig', 'Fox', 54),
27 | ('Isaiah', 'Rich', 92),
28 | ('Myra', 'Small', 56),
29 | ('Sigourney', 'Briggs', 68),
30 | ('Christian', 'Pollard', 20),
31 | ('Aidan', 'Ramsey', 91),
32 | ('Anne', 'Johnston', 78),
33 | ('Sybil', 'Lara', 82),
34 | ('Astra', 'Burgess', 33),
35 | ('Harlan', 'Short', 76),
36 | ('Wyoming', 'Bruce', 44),
37 | ('Martena', 'Hewitt', 52),
38 | ('Gavin', 'Middleton', 99),
39 | ('Angelica', 'Mcfarland', 94),
40 | ('Rama', 'Bauer', 76),
41 | ('Ishmael', 'Lyons', 33),
42 | ('Laith', 'Todd', 31),
43 | ('Finn', 'Schultz', 59),
44 | ('Brock', 'Schroeder', 37),
45 | ('Britanney', 'Trujillo', 57),
46 | ('Evelyn', 'Mcneil', 90),
47 | ('Jada', 'Ray', 19),
48 | ('Abel', 'Watkins', 23),
49 | ('Leonard', 'Martin', 10),
50 | ('Karleigh', 'Velez', 26),
51 | ('Shoshana', 'Joyner', 49),
52 | ('Bryar', 'Bush', 83),
53 | ('Hayfa', 'Foster', 28),
54 | ('Shoshana', 'Giles', 66),
55 | ('Amos', 'Owen', 46),
56 | ('Erin', 'Slater', 36),
57 | ('Kalia', 'Mason', 73),
58 | ('Amber', 'Livingston', 62),
59 | ('Ralph', 'Mayo', 86),
60 | ('Brenden', 'Evans', 27),
61 | ('Noble', 'Mcclure', 75),
62 | ('Hiram', 'Holland', 20),
63 | ('Zachary', 'Diaz', 24),
64 | ('Nigel', 'Santos', 100),
65 | ('Dominic', 'Ingram', 60),
66 | ('Aline', 'Caldwell', 35),
67 | ('Mara', 'Clarke', 65),
68 | ('Cora', 'Tyson', 47),
69 | ('Avram', 'Compton', 87),
70 | ('Bruno', 'Tanner', 95),
71 | ('Roth', 'Sharpe', 18),
72 | ('Zelenia', 'Nicholson', 84),
73 | ('Kitra', 'Olson', 17),
74 | ('Ina', 'Knox', 14),
75 | ('Quin', 'Durham', 46),
76 | ('Noelani', 'Cruz', 32),
77 | ('Kiayada', 'Kerr', 20),
78 | ('Harrison', 'Maldonado', 18),
79 | ('Clark', 'Baird', 81),
80 | ('Nina', 'Roy', 20),
81 | ('Barry', 'William', 73),
82 | ('Samuel', 'Frye', 92),
83 | ('Kato', 'French', 90),
84 | ('Ursa', 'Heath', 31),
85 | ('Hollee', 'Fields', 79),
86 | ('Fulton', 'Pollard', 94),
87 | ('Brody', 'Fleming', 10),
88 | ('Brock', 'Garrison', 23),
89 | ('Lani', 'Fields', 20),
90 | ('Sybill', 'Hickman', 59),
91 | ('Todd', 'Goodwin', 14),
92 | ('Ezekiel', 'Solomon', 11),
93 | ('Karly', 'Juarez', 100),
94 | ('Portia', 'Grant', 31),
95 | ('Wang', 'Sherman', 52),
96 | ('Justine', 'Perkins', 25),
97 | ('Ryan', 'Stevenson', 85),
98 | ('Rogan', 'Johns', 50),
99 | ('Carson', 'West', 48),
100 | ('Ulysses', 'Garrison', 87),
101 | ('Chiquita', 'Kramer', 48),
102 | ('Dahlia', 'Conner', 73),
103 | ('Sara', 'Becker', 71),
104 | ('Plato', 'Craig', 17);
--------------------------------------------------------------------------------
/src/main/resources/static/css/style.css:
--------------------------------------------------------------------------------
1 | .pagination-container {
2 | text-align: right;
3 | padding-right: 0;
4 | }
5 |
6 | .page-select {
7 | padding-left: 0;
8 | }
9 |
10 | .disabled {
11 | pointer-events: none;
12 | opacity: 0.5;
13 | }
14 |
15 | .pointer-disabled {
16 | pointer-events: none;
17 | }
--------------------------------------------------------------------------------
/src/main/resources/static/js/app.js:
--------------------------------------------------------------------------------
1 | $(document).ready(() => {
2 | changePageAndSize();
3 | });
4 |
5 | changePageAndSize = () => {
6 | $('#pageSizeSelect').change(evt => {
7 | window.location.replace(`/?pageSize=${evt.target.value}&page=1`);
8 | });
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/resources/templates/persons.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Persons
7 |
8 |
9 |
10 |
11 |
Persons
12 |
13 |
14 |
15 |
16 | First name |
17 | Last name |
18 | Age |
19 |
20 |
21 | |
22 | |
23 | |
24 |
25 |
26 |
27 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/test/java/org/paginationdemo/ApplicationTests.java:
--------------------------------------------------------------------------------
1 | package org.paginationdemo;
2 |
3 | import org.junit.Test;
4 | import org.junit.runner.RunWith;
5 | import org.springframework.boot.test.context.SpringBootTest;
6 | import org.springframework.test.context.junit4.SpringRunner;
7 |
8 | @RunWith(SpringRunner.class)
9 | @SpringBootTest
10 | public class ApplicationTests {
11 |
12 | @Test
13 | public void contextLoads() {
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------