10. Spring/BOOT

09. 스프링 부트 (Spring Boot) - thymeleaf 페이징 처리 Pageable [미완성]

THE HEYDAZE 2020. 11. 19. 19:33
OS Windows 10 Home 64bit 버전 2004 (OS 빌드 19041.630)
Framework Spring Boot 2.3.3 RERELEASE
EditTool Inellij IDEA 2020.1.3
BuildTool Gradle

 

# 참고 사이트
 

스프링 부트, JPA, Thymeleaf를 이용한 페이징 처리 4 - 페이징 구현 (화면)

이제 Controller에서 데이터를 model에 담아 view로 넘겼기 때문에 마지막으로 이전에 생성한 view에서 paging 로직을 개발하면된다. 게시물 리스트 화면에 뿌리기 먼저 게시물 리스트를 화면에 보여주

ivvve.github.io

 

# 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

PageRequest 는 Pageable 인터페이스를 구현 한 클래스이다

 

# 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>&laquo;</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">&lt;</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">&gt;</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>&raquo;</span>
                    <span class="sr-only">First</span>
                </a>
            </li>

        </ul>
    </nav>
</div>

 

[설명]

 

+ 개인적으로 다른 템플릿 엔진보다 thymeleaf 템플릿 엔진이 어려운 것 같습니다..

# Vue (준비중)

github.com/akageun/hello-bbs

 

akageun/hello-bbs

Spring Boot & Vue.js를 사용한 게시판 프로젝트입니다. Contribute to akageun/hello-bbs development by creating an account on GitHub.

github.com

daily-life-of-bsh.tistory.com/208

 

Vue + Spring Pagination(Paging) 구현

Vue와 Spring을 이용하여 Paging을 다음과 같은 페이징을 구현하는 방법을 알아보겠습니다. Vue 프로젝트의 파일 구조와 router.js 의 코드입니다. import Vue from "vue"; import VueRouter from "vue-router";..

daily-life-of-bsh.tistory.com

bezkoder.com/vue-pagination-axios/

 

Vue Pagination with Axios and API (Server Side pagination) example - BezKoder

In this tutorial, I will show you how to make Pagination in a Vue.js Application with existing API (Server Side pagination) using Axios and Bootstrap-Vue. Related Posts: – Vue.js 2 CRUD Application with Vue Router & Axios – Vue.js JWT Authentication wi

bezkoder.com

 

[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">&laquo;</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">&raquo;</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()