VOOZH about

URL: https://www.javacodegeeks.com/2013/03/implement-bootstrap-pagination-with-spring-data-and-thymeleaf.html

⇱ Implement Bootstrap Pagination With Spring Data And Thymeleaf


Twitter Bootstrap has a very nice pagination UI, and here I will show you how to implement it with Spring Data Web pagination function and Thymeleaf conditional evaluation features.

Standard Pagination in Bootstrap

Simple pagination inspired by Rdio, great for apps and search results. The large block is hard to miss, easily scalable, and provides large click areas.

👁 pic

The original source code to display pagination from Bootstrap document is very simple:

<div class='pagination'>
 <ul>
 <li><a href='#'>Prev</a></li>
 <li><a href='#'>1</a></li>
 <li><a href='#'>2</a></li>
 <li><a href='#'>3</a></li>
 <li><a href='#'>4</a></li>
 <li><a href='#'>5</a></li>
 <li><a href='#'>Next</a></li>
 </ul>
</div>

You can see this is just a mock up code, and to make it display page number dynamically with correct hyperlink URL, I need to do many changes to my existing code. So let’s start from bottom up, change domain layer first, then application service layer, presentation layer. Finally the configuration to glue them together.

Domain Layer Changes

The only change in domain layer is at BlogPostRepository. Before it has a method to retrieve a list of published BlogPost sorted by publishedTime:

public interface BlogPostRepository extends MongoRepository<BlogPost, String>{
...
 List<BlogPost> findByPublishedIsTrueOrderByPublishedTimeDesc();
...
}

Now we need to get paginated result list. With Spring Data Page, we will return Page<BlogPost> instead of List<BlogPost>, and pass Pageable parameter:

public interface BlogPostRepository extends MongoRepository<BlogPost, String>{
...
 Page<BlogPost> findByPublishedIsTrueOrderByPublishedTimeDesc(Pageable pageable);
...
}

Application Service Layer Changes:

The applicaiton service layer change is also very simple, just using new function from BlogPostRepository:

BlogService interface

public interface BlogService {
...
 Page<BlogPost> getAllPublishedPosts(Pageable pageable);
...
}

BlogServiceImpl class

public class BlogServiceImpl implements BlogService {
...
 private final BlogPostRepository blogPostRepository;
...
 @Override
 public Page<BlogPost> getAllPublishedPosts(Pageable pageable) {
 Page<BlogPost> blogList = blogPostRepository.findByPublishedIsTrueOrderByPublishedTimeDesc(pageable);
 return blogList;
 }
...
}

Presentation Layer Changes:

Spring Data Page interface has many nice functions to get current page number, get total pages, etc. But it’s still lack of ways to let me only display partial page range of total pagination. So I created an adapter class to wrap Sprng Data Page interface with additional features.

public class PageWrapper<T> {
 public static final int MAX_PAGE_ITEM_DISPLAY = 5;
 private Page<T> page;
 private List<PageItem> items;
 private int currentNumber;
 private String url;

 public String getUrl() {
 return url;
 }

 public void setUrl(String url) {
 this.url = url;
 }

 public PageWrapper(Page<T> page, String url){
 this.page = page;
 this.url = url;
 items = new ArrayList<PageItem>();

 currentNumber = page.getNumber() + 1; //start from 1 to match page.page

 int start, size;
 if (page.getTotalPages() <= MAX_PAGE_ITEM_DISPLAY){
 start = 1;
 size = page.getTotalPages();
 } else {
 if (currentNumber <= MAX_PAGE_ITEM_DISPLAY - MAX_PAGE_ITEM_DISPLAY/2){
 start = 1;
 size = MAX_PAGE_ITEM_DISPLAY;
 } else if (currentNumber >= page.getTotalPages() - MAX_PAGE_ITEM_DISPLAY/2){
 start = page.getTotalPages() - MAX_PAGE_ITEM_DISPLAY + 1;
 size = MAX_PAGE_ITEM_DISPLAY;
 } else {
 start = currentNumber - MAX_PAGE_ITEM_DISPLAY/2;
 size = MAX_PAGE_ITEM_DISPLAY;
 }
 }

 for (int i = 0; i<size; i++){
 items.add(new PageItem(start+i, (start+i)==currentNumber));
 }
 }

 public List<PageItem> getItems(){
 return items;
 }

 public int getNumber(){
 return currentNumber;
 }

 public List<T> getContent(){
 return page.getContent();
 }

 public int getSize(){
 return page.getSize();
 }

 public int getTotalPages(){
 return page.getTotalPages();
 }

 public boolean isFirstPage(){
 return page.isFirstPage();
 }

 public boolean isLastPage(){
 return page.isLastPage();
 }

 public boolean isHasPreviousPage(){
 return page.hasPreviousPage();
 }

 public boolean isHasNextPage(){
 return page.hasNextPage();
 }

 public class PageItem {
 private int number;
 private boolean current;
 public PageItem(int number, boolean current){
 this.number = number;
 this.current = current;
 }

 public int getNumber(){
 return this.number;
 }

 public boolean isCurrent(){
 return this.current;
 }
 }
}

With this PageWrapper, we can wrap Page<BlogPost> returned from BlogService and put it into SpringMVC UI model. See the controller code for the blog page:

@Controller
public class BlogController
...
 @RequestMapping(value = '/blog', method = RequestMethod.GET)
 public String blog(Model uiModel, Pageable pageable) {
 PageWrapper<BlogPost> page = new PageWrapper<BlogPost>
 	(blogService.getAllPublishedPosts(pageable), '/blog');
 uiModel.addAttribute('page', page);
 return 'blog';
 }
...
}

The Pageable is passed in from PageableArgumentResolver, which I will explain later. Another trick is I also pass the view URL to PageWrapper, and it can be used to construct Thymeleaf hyperlinks in pagination bar.

Since my PageWrapper is very generic, I created an html fragment for pagination bar, so I can use it for anywhere in my application pages when pagination needed. This fragment html uses Thymeleaf th:if to dynamically switch between static text or hyperlink based on if the link is disabled or not. And it uses th:href to construct URL with correct page number and page size.

<!-- Pagination Bar -->
<div th:fragment='paginationbar'>
 <div class='pagination pagination-centered'>
 <ul>
 <li th:class='${page.firstPage}? 'disabled' : '''>
 <span th:if='${page.firstPage}'>← First</span>
 <a th:if='${not page.firstPage}' th:href='@{${page.url}(page.page=1,page.size=${page.size})}'>← First</a>
 </li>
 <li th:class='${page.hasPreviousPage}? '' : 'disabled''>
 <span th:if='${not page.hasPreviousPage}'>«</span>
 <a th:if='${page.hasPreviousPage}' th:href='@{${page.url}(page.page=${page.number-1},page.size=${page.size})}' title='Go to previous page'>«</a>
 </li>
 <li th:each='item : ${page.items}' th:class='${item.current}? 'active' : '''>
 <span th:if='${item.current}' th:text='${item.number}'>1</span>
 <a th:if='${not item.current}' th:href='@{${page.url}(page.page=${item.number},page.size=${page.size})}'><span th:text='${item.number}'>1</span></a>
 </li>
 <li th:class='${page.hasNextPage}? '' : 'disabled''>
 <span th:if='${not page.hasNextPage}'>»</span>
 <a th:if='${page.hasNextPage}' th:href='@{${page.url}(page.page=${page.number+1},page.size=${page.size})}' title='Go to next page'>»</a>
 </li>
 <li th:class='${page.lastPage}? 'disabled' : '''>
 <span th:if='${page.lastPage}'>Last →</span>
 <a th:if='${not page.lastPage}' th:href='@{${page.url}(page.page=${page.totalPages},page.size=${page.size})}'>Last →</a>
 </li>
 </ul>
 </div>
</div>

Spring Configuration Change

The last step is to put them all together. Fortunately I did some research before I updated my code. There is a very good blog post Pagination with Spring MVC, Spring Data and Java Config by Doug Haber. In his blog, Doug mentioned several gotchas, especially the Pageable parameter needs some configuration magic:

In order for Spring to know how to convert the parameter to a Pageable object you need to configure a HandlerMethodArgumentResolver. Spring Data provides a PageableArgumentResolver but it uses the old ArgumentResolver interface instead of the new (Spring 3.1) HandlerMethodArgumentResolver interface. The XML config can handle this discrepancy for us, but since we’re using Java Config we have to do it a little more manually. Luckily this can be easily resolved if you know the right magic incantation…
Doug Haber

With Doug’s help, I added this argument resolver to my WebConfig class:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = 'com.jiwhiz.blog.web')
public class WebConfig extends WebMvcConfigurerAdapter {
...
 @Override
 public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
 PageableArgumentResolver resolver = new PageableArgumentResolver();
 resolver.setFallbackPagable(new PageRequest(1, 5));
 argumentResolvers.add(new ServletWebArgumentResolverAdapter(resolver));
 }
...
}

After all those changes, my Blog list will have pagination bar at the top and bottom of the page, and it always have at most 5 page numbers, with current number at the middle and disabled. The pagination bar also has First and Previous links at the beginning, Next and Last links at the end. I also used it in my admin pages, for user list and comment list, and it works very well.
 

Reference: Implement Bootstrap Pagination With Spring Data And Thymeleaf from our JCG partner Yuan Ji at the Jiwhiz blog.

Do you want to know how to develop your skillset to become a Java Rockstar?
Subscribe to our newsletter to start Rocking right now!
To get you started we give you our best selling eBooks for FREE!
1. JPA Mini Book
2. JVM Troubleshooting Guide
3. JUnit Tutorial for Unit Testing
4. Java Annotations Tutorial
5. Java Interview Questions
6. Spring Interview Questions
7. Android UI Design
and many more ....
I agree to the Terms and Privacy Policy

Thank you!

We will contact you soon.

👁 Photo of Yuan Ji
Yuan Ji
March 5th, 2013Last Updated: March 4th, 2013
16 1,449 4 minutes read

Yuan Ji

Yuan is a passionate Java programmer and open source evangelist. He is eager to learn new technologies and loves clean and beautiful application design. He lives in Edmonton, Canada as an independent consultant and contractor.
Subscribe

This site uses Akismet to reduce spam. Learn how your comment data is processed.

16 Comments
Oldest
Newest Most Voted
13 years ago

Nice article, thanks for sharing.

0
Reply
wangjw
4 years ago
Reply to  Dashout IN

hello friend, I am from China

0
Reply
Clément
12 years ago

Thank you ! This solution is really good and is going to same me a lot of time.

I just had to change my spring config, by adding :

and it worked perfectly.

0
Reply
Jean Duarte
12 years ago

Great job! Works perfectly…

Tkank you.

0
Reply
surya
11 years ago

hi..can u please provide full source…

0
Reply
Seymour Birkhoff
11 years ago

The nested if in the PageWrapper constructor does the same size assignment under all three conditions and x-x/2 can be simplified to x/2.

0
Reply
Kul Bhushan Prasad
11 years ago

Hi, Thanks for the Good solution. I have faced some issues while implementing the same in my bootstrap pagination with spring data with thymeleaf program. Mentioning the same and solution to fix the same. 1) In addArgumentResolvers method PageableArgumentResolver is deprecated now. so i used PageableHandlerMethodArgumentResolver example: PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver(); resolver.setPageParameterName(“page.page”); resolver.setSizeParameterName(“page.size”); resolver.setFallbackPageable(new PageRequest(0, 50)); resolver.setOneIndexedParameters(true); argumentResolvers.add(resolver); super.addArgumentResolvers(argumentResolvers); 2) I have also faced the issues with block also. It was showing syntax issues, also there was a issue in drawing(Active pages were coming after the bar as disabled). so i have changed the block a bit, mentioning the… Read more »

0
Reply
Shyam
11 years ago

Is it possible to maintain pagination in view pages. Like,i have user search screen and transaction search page.so i need to main two pagination in application.Can you help me on it.

-1
Reply
Shyam
11 years ago

Is it possible to maintain 2 paginations in 2 view pages. Like,i have user search screen and transaction search page.so i need to maintain two paginations in application.Can you help me on it.

0
Reply
Rogerio
10 years ago

Hello thanks for the good solution, the PageWraper works like a charm, bu my paginationBar dont rende with css style. Any idea why this happen?

0
Reply
Kevin
10 years ago

Great tutorial.. it’s working perfectly until I get to the last page. I get this error.

java.lang.IndexOutOfBoundsException: Index: 3, Size: 3

Could that be because there’s not exactly 10 items in the last page? im showing 10 rows per page.

any help would be greatly appreciated

Kevin

0
Reply
cst1992
9 years ago

Hi, nice tutorial! I’d like to add that there is no numberOfElements and totalElements in this response though. It needs to be added, otherwise there is no way to show total records and number of records on-page in the UI.

0
Reply
Shadab
8 years ago
Reply to  cst1992
0
Reply
cst1992
9 years ago

Also, the PageItem class needs to be static for the serialization to work.

0
Reply
ADK
9 years ago

hii i m first time doing pagination using spring …how can i do … my project

0
Reply
celi78
8 years ago

Hi.

Error on BlogController

Cannot make a static reference to the non-static method getAllPublishedPosts(Pageable) from the type BlogService.

Can you give solution to fix this issue?

0
Reply
Back to top button
Close
wpDiscuz