Implement paging function with Spring Boot + Thymeleaf

You can easily implement screen paging functionality with Spring Boot + Thymeleaf. Screen image after completion スクリーンショット 2017-01-21 23.25.18.png

Verification environment

Implementation

First is the Domain layer.

@Entity
@Table(name="word_info")
public class Word implements Serializable {

	private static final long serialVersionUID = -870708489937857961L;
	
	@Id
	@GeneratedValue(strategy=GenerationType.TABLE, generator="seqTable")
	@TableGenerator(name="seqTable", table="seq_table", pkColumnName="seq_name", pkColumnValue="word_seq", valueColumnName="seq_value")
	@Column(name="id")
	private Long id;
	
	@Column(name="word")
	private String word;
	
	@Column(name="meaning")
	private String meaning;
	
	@Column(name="example")
	private String example;

	//get / set omitted
	...
}

Define a JPA repository class to get the list. Use the Page class instead of List.

@Repository
public interface WordRepository extends CrudRepository<Word, Long>{
	
	public Page<Word> findAll(Pageable pageable);

}

Next is the Service layer.

@Service
public class WordService {
	
	@Autowired
	private WordRepository wordRepo;
	
	public Page<Word> getAllWord(Pageable pageable) {

		return wordRepo.findAll(pageable);
	}

}

Presentation layer.

@Controller
public class MainController {
	
	@Autowired
	private WordService wordService;
	
	@RequestMapping(value="/word/wordList", method=RequestMethod.GET)
	public String getWordList(Model model, Pageable pageable) {
		Page<Word> wordsPage = wordService.getAllWord(pageable);
		model.addAttribute("page", wordsPage);
		model.addAttribute("words", wordsPage.getContent());
		model.addAttribute("url", "/word/wordList");
		
		return "/word/wordList";
	}
}

Setting the number of items to be displayed

Set the maximum number of items to be displayed for each page.

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

  @Override
  public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
      PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
      //Number of items to be displayed per page
      resolver.setFallbackPageable(new PageRequest(0, 5));
      argumentResolvers.add(resolver);
      super.addArgumentResolvers(argumentResolvers);
  }

}

Finally, the implementation on the Thymeleaf side.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
	  xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta charset="UTF-8" />
		<link th:substituteby="common/header :: common_header"/>
		<title>Word List</title>
	</head>
	<body>
		<table border="1">
			<tr>
				<th>ID</th>
				<th>WORD</th>
				<th>MEANING</th>
				<th>EXAMPLE</th>
			</tr>
			<tr th:each="word:${words}">
				<td th:text="${word.id}"></td>
				<td th:text="${word.word}"></td>
				<td th:text="${word.meaning}"></td>
				<td th:text="${word.example}"></td>
			</tr>
		</table>
		
		<div th:fragment='paginationbar'>
			<ul>
				<li th:class="${page.first} ? 'disabled':''" style="display:inline">
					<span th:if="${page.first}">← First</span>
					<a th:if="${not page.first}" th:href="@{${url}(page=0)}">← First</a>
				</li>
				<li th:each='i : ${#numbers.sequence(0, page.totalPages-1)}' th:class="(${i}==${page.number})? 'active' : ''" style="display:inline">
                	<span th:if='${i}==${page.number}' th:text='${i+1}'>1</span>
               	 	<a th:if='${i}!=${page.number}' th:href="@{${url}(page=${i})}">
               	 		<span th:text='${i+1}'>1</span>
               	 	</a>
                </li>
				<li th:class="${page.last} ? 'disabled':''" style="display:inline">
					<span th:if="${page.last}">End ➝</span>
					<a th:if="${not page.last}" th:href="@{${url}(page=(${page.totalPages}-1))}">End ➝</a>
				</li>
			</ul>
		</div>
	</body>
</html>

Customize

In the case where the number of pages is large as shown below, if you want to display only some page numbers, you can create a wrapper class for Page and customize it. 2017-12-08_092643.jpg

Wrapper class implementation

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;

        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.isFirst();
    }

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

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

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

    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;
        }
    }
}

Control class change

Use the wrapper class instead of Page.

@Controller
public class MainController {
	
	@Autowired
	private WordService wordService;

	@RequestMapping("/word/register")
	public String wordRegister(WordForm wordForm) {
		wordService.addWord(wordForm);
		return "/word/wordRegister";
	}
	
	@RequestMapping(value="/word/wordList", method=RequestMethod.GET)
	public String getWordList(Model model, Pageable pageable) {
		Page<Word> wordPage = wordService.getAllWord(pageable);
		PageWrapper<Word> page = new PageWrapper<Word>(wordPage, "/word/wordList");
		model.addAttribute("page", page);
		model.addAttribute("words", page.getContent());
		
		return "/word/wordList";
	}
}

Thymeleaf

Change the paging part as follows.

	...

		<div th:fragment='paginationbar'>
			<ul class='pagination pagination-centered'>
				<li th:class="${page.firstPage}?'disabled':''" style="display:inline">
					<span th:if='${page.firstPage}'>← First</span>
					<a th:if='${not page.firstPage}' th:href='@{${page.url}(page=0,size=${page.size})}'>← First</a>
				</li>
				<li th:class="${page.hasPreviousPage}? '' : 'disabled'" style="display:inline">
					<span th:if='${not page.hasPreviousPage}'>«</span>
					<a th:if='${page.hasPreviousPage}' th:href='@{${page.url}(page=${page.number-2},size=${page.size})}'>«</a>
				</li>
	                
	                
				<li th:each='item : ${page.items}' th:class="${item.current}? 'active' : ''" style="display:inline">
					<span th:if='${item.current}' th:text='${item.number}'>1</span>
					<a th:if='${not item.current}' th:href='@{${page.url}(page=${item.number-1},size=${page.size})}'>
					<span th:text='${item.number}'>1</span>
					</a>
				</li>
				<li th:class="${page.hasNextPage}? '' : 'disabled'" style="display:inline">
					<span th:if='${not page.hasNextPage}'>»</span>
					<a th:if='${page.hasNextPage}' th:href='@{${page.url}(page=${page.number},size=${page.size})}'>»</a>
				</li>
				<li th:class="${page.lastPage}? 'disabled' : ''" style="display:inline">
					<span th:if='${page.lastPage}'>End ➝</span>
					<a th:if='${not page.lastPage}' th:href='@{${page.url}(page=${page.totalPages - 1},size=${page.size})}'>End ➝</a>
				</li>
			</ul>
		</div>
	...

Reference: https://github.com/mtiger2k/pageableSpringBootDataJPA

Recommended Posts

Implement paging function with Spring Boot + Thymeleaf
Implement CRUD with Spring Boot + Thymeleaf + MySQL
Implement GraphQL with Spring Boot
Try to implement login function with Spring Boot
Run WEB application with Spring Boot + Thymeleaf
Download with Spring Boot
Create CRUD apps with Spring Boot 2 + Thymeleaf + MyBatis
Create your own Utility with Thymeleaf with Spring Boot
[Introduction to Spring Boot] Authentication function with Spring Security
Generate barcode with Spring Boot
Hello World with Spring Boot
Get started with Spring boot
Hello World with Spring Boot!
Run LIFF with Spring Boot
SNS login with Spring Boot
Implement a simple Rest API with Spring Security with Spring Boot 2.0
[Java] Thymeleaf Basic (Spring Boot)
File upload with Spring Boot
Spring Boot starting with copy
Login function with Spring Security
Spring Boot starting with Docker
Hello World with Spring Boot
Set cookies with Spring Boot
[Spring Boot] Easy paging recipe
Use Spring JDBC with Spring Boot
Add module with Spring Boot
Getting Started with Spring Boot
Create microservices with Spring Boot
Send email with spring boot
Implement search function with form_with
Handle Java 8 date and time API with Thymeleaf with Spring Boot
Implement REST API with Spring Boot and JPA (Application Layer)
Implement REST API with Spring Boot and JPA (Infrastructure layer)
Until INSERT and SELECT to Postgres with Spring boot and thymeleaf
Use thymeleaf3 with parent without specifying spring-boot-starter-parent in Spring Boot
Implement REST API with Spring Boot and JPA (domain layer)
Implement a simple Rest API with Spring Security & JWT with Spring Boot 2.0
Use Basic Authentication with Spring Boot
Implemented authentication function with Spring Security ②
Implement text link with Springboot + Thymeleaf
gRPC on Spring Boot with grpc-spring-boot-starter
Implemented authentication function with Spring Security ③
Create an app with Spring Boot 2
Hot deploy with Spring Boot development
Database linkage with doma2 (Spring boot)
Spring Boot programming with VS Code
Until "Hello World" with Spring Boot
Inquiry application creation with Spring Boot
Implemented authentication function with Spring Security ①
Get validation results with Spring Boot
Implement file download with Spring MVC
(Intellij) Hello World with Spring Boot
Create an app with Spring Boot
Implement REST API in Spring Boot
Spring profile function, and Spring Boot application.properties
Google Cloud Platform with Spring Boot 2.0.0
Check date correlation with Spring Boot
I tried GraphQL with Spring Boot
[Java] LINE integration with Spring Boot
Beginning with Spring Boot 0. Use Spring CLI
I tried Flyway with Spring Boot