SpringBoot #6 SpringBoot+React 기반 간단한 게시판 생성하기 #5 웹 페이지에 페이지 번호 추가 및 기능 구현하기

2021. 10. 14. 15:16JAVA/Spring

이전 글

https://yonghwankim-dev.tistory.com/144

 

SpringBoot #6 SpringBoot+React 기반 간단한 게시판 생성하기 #4 게시판 관련 컴포넌트 및 Router 컴포넌트

이전글 https://yonghwankim-dev.tistory.com/143 SpringBoot #6 SpringBoot+React 기반 간단한 게시판 생성하기 #3 React 프로젝트 생성 이전글 https://yonghwankim-dev.tistory.com/146 SpringBoot #6 SpringBo..

yonghwankim-dev.tistory.com

 

개요

이전 글에서는 React+SpringBoot+MySQL 연동을 완료하여 데이터베이스에 존재하는 게시판의 게시물 샘플 데이터를 조회하여 웹 페이지에 출력하는 것을 구현하였습니다. 이번 글에서는 게시물의 페이지 번호를 웹 페에지에 출력하도록 하고 기능을 구현하는 것을 목표로 합니다.

 

1. React : 페이지 번호 구현

src/component/webBoard/WebBoardListComponent.jsx 수정

import React, {Component} from 'react';
import ApiService from "../../ApiService";
import queryStirng from 'query-string';

class WebBoardListComponent extends Component{
    constructor(props){
        super(props);

        this.state = {
            result : null,      
            prevPage : null,
            nextPage : null,
            pageList : [],
            boards : [],
            page : 1,
            size : 10,
            type : "",
            keyword : "",
        }
        
    }

    componentDidMount(){
        this.reloadWebBoardList(this.state.page, this.state.size, this.state.type, this.state.keyword);
    }

    reloadWebBoardList = (page=1, size=10, type="", keyword="")=>{
        ApiService.fetchWebBoards(page, size, type, keyword)
                    .then(res=>{
                        this.setState({
                                        result : res.data,
                                        prevPage : res.data.prevPage,
                                        nextPage : res.data.nextPage,
                                        pageList : res.data.pageList,
                                        boards:res.data.result.content
                                    })
                    })
                    .catch(err=>{console.log("reloadWebBoardList() Error!",err);});
        
    }

    onChangePage = (p)=>{
        this.setState({page : p});
        this.reloadWebBoardList(p,this.state.size, this.state.type, this.state.keyword);
    }
    onChangeType = (e)=>{
        this.setState({type : e.target.value});
    }
    onChangeKeyword = (e)=>{
        this.setState({keyword : e.target.value});
    }
    onClickSearch = ()=>{
        this.reloadWebBoardList(this.state.page, this.state.size, this.state.type, this.state.keyword);
    }

    render(){
        return(
            <>
            <div>
                <h2>WebBoard List</h2>
                <table>
                    <thead>
                        <tr>
                            <th>Bno</th>
                            <th>Title</th>
                            <th>Wirter</th>
                            <th>Content</th>
                            <th>Regdate</th>
                            <th>Updatedate</th>
                        </tr>
                    </thead>
                    <tbody>
                        {
                            this.state.boards.map( board=>
                                    <tr key={board.bno}>
                                        <td>{board.bno}</td>
                                        <td>{board.title}</td>
                                        <td>{board.writer}</td>
                                        <td>{board.content}</td>
                                        <td>{board.regdate}</td>
                                        <td>{board.updatedate}</td>
                                    </tr>
                                )
                        }
                    </tbody>
                </table>

                {/* search */}
                <div>
                    <select name="searchType" onChange={this.onChangeType}>
                        <option value="">--</option>
                        <option value='t'>Title</option>
                        <option value='w'>Writer</option>
                        <option value='c'>Content</option>
                    </select>
                    <input type="text" onChange={this.onChangeKeyword}/>
                    <button onClick={this.onClickSearch}>Search</button>
                </div>
            </div>
            <div>
                {/* pagination */}
                <ul>                 
                    {
                        /* PREV 버튼 */
                        this.state.prevPage===null ? null : <li><button onClick={()=>{this.onChangePage(this.state.prevPage.pageNumber+1)}}>PREV {this.state.prevPage.pageNumber+1}</button></li>
                    }
                    {
                        /* 페이지 번호 버튼 */
                        this.state.pageList.map(page =>
                                                        <li key={page.pageNumber+1}>
                                                            {
                                                                this.state.result.currentPageNum-1===page.pageNumber ? 
                                                                    <button onClick={()=>{this.onChangePage(page.pageNumber+1)}} style={{color:"red"}}>{page.pageNumber+1}</button>
                                                                    :
                                                                    <button onClick={()=>{this.onChangePage(page.pageNumber+1)}}>{page.pageNumber+1}</button>
                                                                    
                                                            }
                                                        </li> 
                                                        
                                                )
                    }
                    {
                        /* NEXT 버튼 */
                        this.state.nextPage===null ? null : <li><button onClick={()=>{this.onChangePage(this.state.nextPage.pageNumber+1)}}>NEXT {this.state.nextPage.pageNumber+1}</button></li>
                    }
                </ul>
            </div>

            </>
        );
    }
}

export default WebBoardListComponent;

 

2. React : ApiService.js 수정

src/ApiService.js 수정

import axios from 'axios';

const BOARD_API_BASE_URL = "http://localhost:8080/boards";

class ApiService{

    fetchWebBoards(page, size, type, keyword){
        return axios.get(BOARD_API_BASE_URL + "/list?page="+page+"&size="+size+"&type="+type+"&keyword="+keyword);
    }
    
}

export default new ApiService();

 

3. SpringBoot : 페이지 번호 출력

페이지 처리에는 Page<WebBoard>의 getPageable()을 이용해서 Pageable 타입의 객체를 활용합니다. Pageable 객체는 다음과 같은 구조로 이루어져 있습니다.

주어지는 Pageable 객체를 이용해서 화면에 간단하게 이전 페이지와 다음 페이지만을 보여주는 것이라면 문제가 없지만, 일반적인 웹 페이지처럼 페이지의 번호를 보여주려면 시작 페이지까지 몇 번 prevOrFirst()를 해야하고, next()를 해야 하는지 계산해야합니다.

 

페이지 번호를 출력하려면 프로젝트에서 org.zerock.vo 패키지에 PageMaker라는 별도의 클래스를 이용해, 페이지 번호 출력에 필요한 정보들을 처리하도록 저장합니다.

 

org.zerock.vo.PageMaker 클래스 생성

 

package org.zerock.vo;

import java.util.ArrayList;
import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import lombok.Getter;
import lombok.ToString;
import lombok.extern.java.Log;

@Getter
@ToString(exclude = "pageList")
@Log
public class PageMaker<T> {
	private Page<T> result;
	
	private Pageable prevPage;	// 페이지 목록의 맨 앞인 '이전'으로 이동하는데 필요한 정보를 가진 Pageable
	private Pageable nextPage;	// 페이지 목록의 맨 뒤인 '다음'으로 이동하는데 필요한 정보를 가진 Pageable
	
	private int currentPageNum;	// 현재 페이지 수
	private int totalPageNum;	// 전체 페이지 수
	
	private Pageable currentPage;	// 현재 페이지의 정보를 가진 Pageable
	
	private List<Pageable> pageList;	// 페이지 번호의 시작부터 끝까지의 Pabeable들을 저장한 리스트
	
	public PageMaker(Page<T> result) {
		this.result = result;
		
		this.currentPage = result.getPageable();
		
		this.currentPageNum = result.getNumber() + 1;	// result.getNumber는 0부터 시작
		
		this.totalPageNum = result.getTotalPages();
		
		this.pageList = new ArrayList<Pageable>();
		
		calcPages();
	}
	
	private void calcPages() {
		// currentPageNum==1 => tempEndNum = 10, startNum = 1
		// currentPageNum==11 => tempEndNum = 20, startNum = 11
		// currentPageNum==21 => tempEndNum = 30, startNum = 21
		int tempEndNum = (int) (Math.ceil(this.currentPageNum/10.0)*10);
		int startNum = tempEndNum - 9;
		
		Pageable startPage = this.currentPage;
		
		// 현재 페이지 번호를 기준으로 (현재 페이지 번호-10)번째 Pageable 정보를 가져온다. (이전 버튼)
		for(int i=startNum; i<this.currentPageNum;i++) 
		{
			startPage = startPage.previousOrFirst();
		}
		this.prevPage = startPage.getPageNumber() <= 0 ? null : startPage.previousOrFirst();
		
		log.info("tempEndNum: " + tempEndNum);
		log.info("total: " + totalPageNum);
		
		// 전체페이지 개수가 10페이지단위 종료보다 작은 경우 끝 페이지 수를 변경 
		if(this.totalPageNum < tempEndNum) 
		{
			tempEndNum = this.totalPageNum;
			this.nextPage = null;	// 다음 버튼 비활성화
		}

		// 페이지 정보를 가지는 Pageable 객체를 리스트에 저장
		for(int i=startNum; i<=tempEndNum;i++) 
		{
			pageList.add(startPage);
			startPage = startPage.next();
		}
		// 다음 페이지가 남아있다면 다음 페이지 정보를 가지는 pageable 객체 저장
		this.nextPage = startPage.getPageNumber() +1 < totalPageNum ? startPage : null;
	}
}

org.zerock.controller.WebBoardController.java 수정

@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/boards/")
@Log
public class WebBoardController {
	
	@Autowired
	private WebBoardRepository repo;
	
	@GetMapping("/list")
	public PageMaker<WebBoard> list(PageVO vo) {
	
		Pageable page = vo.makePageable(0, "bno");
		
		Page<WebBoard> result = repo.findAll(repo.makePredicate(null, null), page);
		
		log.info(""+page);
		log.info(""+result);
		
		return new PageMaker<WebBoard>(result);
	}
	
	
}

list() 메서드의 수정된 부분은 기존 Page<WebBoard> 객체를 반환하는 것이 아닌 PageMaker<WebBoard> 객체 타입을 반환하는 것으로 수정되었습니다. 그래서 객체를 생성하면 생성자안에서 필요한 정보를 처리할 것입니다.

 

4. 프로젝트 실행 및 확인

React 및 SpringBoot 프로젝트를 실행하고, 브라우저에서 'http://localhost:3000/boards/list'를 입력하여 페이지 번호가 출력되고 제대로 기능하는지 확인합니다.

아래 실행 결과는 NEXT 11버튼을 클릭하였을 때 결과입니다.

 

References

source code : https://github.com/yonghwankim-dev/SpringBoot-Study
스타트 스프링 부트, 구멍가게 코딩단 지음