09. 스프링 부트 (Spring Boot) - thymeleaf 페이징 처리 Pageable [미완성]
OS | Windows 10 Home 64bit 버전 2004 (OS 빌드 19041.630) |
Framework | Spring Boot 2.3.3 RERELEASE |
EditTool | Inellij IDEA 2020.1.3 |
BuildTool | Gradle |
# 참고 사이트
# API
메소드 | 설명 | 리턴 타입 |
unpaged() | 페이지 매김 설정이 없음을 나타내는 Pageable 인스턴스를 반환합니다 |
Pageable |
isPaged() | 현재 Pageable에 페이지 매기기 정보가 포함되어 있는지 여부를 반환합니다 |
boolean |
isUnpaged() | 현재 {@link Pageable}에 페이지 매기기 정보가 없는지 여부를 반환합니다. |
boolean |
getPageNumber() | 현재 페이지 번호를 리턴합니다 | int |
getPageSize() | 한 페이지당 보여 줄 개수를 리턴합니다 | int |
getOffset() | 기본 페이지 및 페이지 크기에 따라 취할 오프셋을 반환합니다 |
long |
getSort() | 정렬 매개변수를 반환합니다 | Sort |
getSortOr() | 현재Sort 또는 현재 정렬되지 않은 경우 지정된 정렬을 반환합니다. @param 정렬은 null이 아니어야합니다. |
Sort |
next() | 다음 페이지를 요청하는Pageable 을 반환합니다 | Pageable |
previousOrFirst() | 이전 Pageable 또는 현재 페이지가 이미 첫 번째 인 경우 첫 번째 Pageable 를 반환합니다. |
Pageable |
first() | 첫 페이지를 요청하는 @link Pageable 을 반환합니다. | Pageable |
hasPrevious() | 현재 페이지에서 액세스 할 수있는 이전 Pageable 가 있는지 여부를 반환합니다. 현재 Pageable 이 이미 첫 페이지를 참조하는 경우 false 를 반환합니다. |
boolean |
# PageRequest
# Thymeleaf
[결과]
[JAVA 코드]
@Entity
@Getter
@ToString
@NoArgsConstructor
public class Board extends AbstractEntity {
@Column(name = "title", nullable = false)
public String title;
@Lob
@Column(name = "content", nullable = false)
public String content;
@Builder(builderMethodName = "create")
private Board(String title, String content) {
this.title = title;
this.content = content;
}
}
@Controller
@RequiredArgsConstructor
@Slf4j
public class BoardController {
private final BoardService boardService;
@GetMapping("/board")
public String boardPage(Model model, @PageableDefault(page = 0, size = 10, sort = "id", direction = Sort.Direction.DESC) Pageable pageable) {
log.info("[GET] boardPage");
Page<Board> findBoards = boardService.findPage(pageable);
model.addAttribute("boards", findBoards);
return "board/board";
}
}
@Service
@Transactional
@RequiredArgsConstructor
public class BoardService {
private final BoardRepository boardRepository;
public Page<Board> findPage(Pageable pageable) {
return boardRepository.findAll(pageable);
}
}
[thymeleaf 코드]
<div th:if="${!boards.isEmpty()}">
<nav
th:with="
pageNumber = ${boards.pageable.pageNumber},
pageSize = ${boards.pageable.pageSize},
totalPages = ${boards.totalPages},
startPage = ${T(Math).floor(pageNumber / pageSize) * pageSize + 1},
tempEndPage = ${startPage + pageSize - 1},
endPage = (${tempEndPage > totalPages ? totalPages : tempEndPage})"
aria-label="Page navigation"
>
<ul class="pagination ">
<li th:classappend="${pageNumber + 1 <= pageSize + 1} ? 'disabled'" class="page-item">
<a class="page-link" th:href="@{/board(page=1)}">
<span>«</span>
<span class="sr-only">First</span>
</a>
</li>
<li th:classappend="${boards.first} ? 'disabled'" class="page-item">
<a class="page-link" th:href="${boards.first} ? '#' : @{/board(page=${#numbers.formatDecimal(startPage - pageSize, 0, -1)})}" aria-label="Previous">
<!-- 개인적으로 int 로 변환하는 법을 몰라서 이렇게 길어졌습니다 -->
<span aria-hidden="true"><</span>
<span class="sr-only">Previous</span>
</a>
</li>
<li th:each="page: ${#numbers.sequence(startPage, endPage)}" th:classappend="${page == pageNumber + 1} ? 'active'" class="page-item">
<a th:text="${page}" class="page-link" th:href="@{/board(page=${page})}"></a>
</li>
<li th:classappend="${boards.last} ? 'disabled'" class="page-item">
<a class="page-link" th:href="${boards.last} ? '#' : @{/board(page=${#numbers.formatDecimal(startPage + pageSize, 0, -1)})}" aria-label="Next">
<span aria-hidden="true">></span>
<span class="sr-only">Next</span>
</a>
</li>
<li th:classappend=" ${T(Math).floor(totalPages/10)*10 <= startPage} ? 'disabled'" class="page-item">
<a class="page-link" th:href="@{/board(page=${totalPages})}">
<span>»</span>
<span class="sr-only">First</span>
</a>
</li>
</ul>
</nav>
</div>
[설명]
+ 개인적으로 다른 템플릿 엔진보다 thymeleaf 템플릿 엔진이 어려운 것 같습니다..
# Vue (준비중)
daily-life-of-bsh.tistory.com/208
bezkoder.com/vue-pagination-axios/
[main.js]
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'
Vue.config.productionTip = false
new Vue({
store,
router,
render: h => h(App),
}).$mount('#app')
[http-common.js]
import axios from 'axios'
export default axios.create({
baseURL: "/api",
headers: {
"Content-type": "application/json"
}
})
[App.vue]
<template>
<div id="app">
<router-view/>
</div>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
}
#nav a {
font-weight: bold;
color: #2c3e50;
}
#nav a.router-link-exact-active {
color: #42b983;
}
</style>
<script>
export default {
}
</script>
[vue.config.js]
module.exports = {
/** 서버 설정 */
devServer: {
port: 3000,
proxy: {
'/api/*': {
target: 'http://localhost:8080'
}
}
}
}
[router.js]
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
},
{
path: '/board',
name: 'board',
component: () => import('../views/Board')
}
]
const router = new VueRouter({
mode: 'history',
routes
})
export default router
[Board.vue]
<template>
<div class="board">
<table class="table table-striped">
<thead class="thead-default">
<tr>
<th>번호</th>
<th>제목</th>
<th>날짜</th>
</tr>
</thead>
<tbody>
<tr v-for="board in boards" :key="board.id">
<td scope="row">{{ board.id }}</td>
<td>{{ board.title }}</td>
<td>{{ board.createdAt }}</td>
</tr>
</tbody>
</table>
<pagination
:page="page"
:totalElements="totalElements"
:size="size"
:first="first"
:last="last"
:totalPages="totalPages"
:pageRange="pageRange"
@change="handlePageChange"
/>
</div>
</template>
<script>
import boardService from "../service/boardService";
import Pagination from "./Pagination";
export default {
name: "Board",
components: { Pagination },
data() {
return {
boards: [/* id title createdAt*/],
searchTitle: '',
page: 1,
totalPages: 1,
totalElements: 0,
size: 10,
first: false,
last: false,
pageRange: []
}
},
created() {
this.retrieveBoards()
},
methods: {
getRequestParams(searchTitle, page, pageSize) {
let params = {}
if (searchTitle) {
params["title"] = searchTitle
}
if (page) {
params["page"] = page
}
if (pageSize) {
params["size"] = pageSize
}
return params
},
/** 기본 데이터 */
retrieveBoards() {
const params = this.getRequestParams(
this.searchTitle,
this.page,
this.pageSize
);
console.log('현재 페이지 번호는 ', this.page)
boardService.getAll(params)
.then((response) => {
const { content, totalElements, number, size, totalPages, first, last } = response.data;
this.boards = content;
this.totalElements = totalElements;
this.page = number + 1
this.size = size
this.first = first
this.last = last
this.totalPages = totalPages
console.log(response.data);
/** pagination*/
const startPage = Math.floor((this.page -1) / this.size ) * this.size + 1
const tempEndPage = startPage + this.size - 1
const endPage = tempEndPage > this.totalPages ? this.totalPages : tempEndPage
let tempArray = []
for (let i = startPage; i <= endPage; i++) {
tempArray.push(i)
}
this.pageRange = [].concat(tempArray)
})
.catch((e) => {
console.log(e);
});
},
/** 페이지 변경 */
handlePageChange(pageNumber) {
this.page = pageNumber
this.retrieveBoards()
},
handlePageSizeChange(event) {
this.pageSize = event.target.value;
this.page = 1;
this.retrieveBoards();
}
}
}
</script>
[Pagination.vue]
<template>
<nav aria-label="Page navigation">
<ul class="pagination">
<li class="page-item" :class="firstCss()">
<a class="page-link" href="javascript:void(0)" aria-label="Previous" @click="prevPage()">
<span aria-hidden="true">«</span>
<span class="sr-only">Previous</span>
</a>
</li>
<li class="page-item" :class="pageActive(pageNumber)" v-for="pageNumber in pageRange" :key="pageNumber">
<a class="page-link" href="javascript:void(0)" @click="changePage(pageNumber)">{{ pageNumber }}</a>
</li>
<li class="page-item" :class="lastCss()">
<a class="page-link" href="javascript:void(0)" aria-label="Next" @click="nextPage()">
<span aria-hidden="true">»</span>
<span class="sr-only">Next</span>
</a>
</li>
</ul>
</nav>
</template>
<script>
export default {
name: 'pagination',
props: ['page', 'totalElements', 'size', 'first', 'last', 'totalPages', 'pageRange'],
methods: {
/** 페이지 변환 */
changePage(pageNumber) {
this.$emit('change', pageNumber)
},
/** 페이지 active CSS */
pageActive(pageNumber) {
return pageNumber === this.page ? 'active' : ''
},
firstCss() {
return this.page < this.size + 1 ? 'disabled' : ''
},
lastCss() {
return Math.floor(this.totalPages / 10) * 10 <= this.pageRange[0] ? 'disabled' : ''
},
prevPage() {
this.$emit('change', this.page - this.size)
},
nextPage() {
this.$emit('change', this.page + this.size)
}
}
}
</script>
[boardService.js]
import http from '../http-common'
class BoardService {
getAll(params) {
return http.get('/boards', { params })
}
}
export default new BoardService()
'10. Spring > BOOT' 카테고리의 다른 글
스프링 부트 MVC - Handler Interceptor (0) | 2022.04.14 |
---|---|
11. 스프링 부트 (Spring Boot 2) & 뷰 (Vue 2)- 환경 구축하기 (0) | 2021.03.26 |
10. 스프링부트 (Spring Boot 2.4.3) - javax Transactional과 spring Transactional (0) | 2021.03.14 |
06. 스프링 부트 (Spring Boot) - 자바 메일 센더 (Java Mail Sender) (0) | 2020.08.29 |
02. 스프링부트 (Spring Boot) Profile, JPA Naming, Exception 전략 (0) | 2020.07.11 |
댓글
이 글 공유하기
다른 글
-
11. 스프링 부트 (Spring Boot 2) & 뷰 (Vue 2)- 환경 구축하기
11. 스프링 부트 (Spring Boot 2) & 뷰 (Vue 2)- 환경 구축하기
2021.03.26 -
10. 스프링부트 (Spring Boot 2.4.3) - javax Transactional과 spring Transactional
10. 스프링부트 (Spring Boot 2.4.3) - javax Transactional과 spring Transactional
2021.03.14 -
06. 스프링 부트 (Spring Boot) - 자바 메일 센더 (Java Mail Sender)
06. 스프링 부트 (Spring Boot) - 자바 메일 센더 (Java Mail Sender)
2020.08.29 -
02. 스프링부트 (Spring Boot) Profile, JPA Naming, Exception 전략
02. 스프링부트 (Spring Boot) Profile, JPA Naming, Exception 전략
2020.07.11