Kim VamPa

[스프링 게시판][6] 페이징 기능 구현(페이지 이동 인터페이스) -1 본문

스프링 프레임워크/게시판 프로젝트

[스프링 게시판][6] 페이징 기능 구현(페이지 이동 인터페이스) -1

Kim VamPa 2021. 3. 1. 10:42
728x90
반응형

Git 주소 : github.com/sjinjin7/Blog_BoardProject

목표

 

페이지 이동 인터페이스 구현

  저번 포스팅에서 서버에 '현재 페이지(pageNum)'와 '페이지 표시 개수(amount)' 정보와 함께 'board/list' url을 요청하면 그에 해당하는 페이지가 뷰(View)에 보이도록 구현을 하였습니다. 하지만 아직 웹 사용자가 페이지를 이동을 위해서는 url에 쿼리 스트링을 추가하여 이동이 가능합니다.

 2개의 포스팅에 거쳐서 사용자가 마우스 클릭을 통해 페이지를 이동 할 수 있도록 해주는 인터페이스를 구현하고자 합니다.

 

그림 1 서울시청 자유게시판

 

 

 

순서

1. PageMakeDTO 작성 

2. 게시물 전체 개수 메소드

3. Controller 처리

4. View 처리

 

 

 

1. PageMakeDTO

 

그림 2

 

 우리는 위와 같이 페이지 이동 인터페이스를 구현을 목표로 합니다. '그림 2'를 보시면 3가지 정보가 필요로 합니다.

 

- 초록색 : 화면에 표시되는 페이지 번호

 

- 노란색 : 현재 보이는 페이지의 '이전 페이지', '다음 페이지'

 

- 보라색 : 현재 페이지 표시

 

 위의 3가지 정보를 서버에서 Model 담아서 뷰로 전송하여, 뷰가 이를 활용하여 화면에 표시하도록 할 것입니다. 위의 3가지 정보를 한 번에 각 단계끼리 교환될 수 있도록 클래스에 정의하여 사용할 것입니다. com.vam.model 패키지에 "PageMakerDTO.java"클래스를 생성 후 아래의 변수들을 선언합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
    /* 시작 페이지 */
    private int startPage;
    
    /* 끝 페이지 */
    private int endPage;
    
    /* 이전 페이지, 다음 페이지 존재유무 */
    private boolean prev, next;
    
    /*전체 게시물 수*/
    private int total;
    
    /* 현재 페이지, 페이지당 게시물 표시수 정보 */
    private Criteria cri;
 

 

그림 3

 

- startPage, endPage : 화면에 표시되는 페이지 '시작 번호' & '끝 번호'에 대한 정보입니다.

 

- prev, next : 화면에 보이는 10개 페이지의 '이전 페이지', '다음 페이지' 존재 유무에 대한 정보입니다.

 

- total : '전체 페이지' 정보입니다. 해당 정보가 있어야 'startPage', 'endPage', 'prev', 'next'의 값을 구할 수 있어서 선언하였습니다.

 

- cri : Criteria 클래스의 pageNum(현재 페이지) 변수 값을 얻기 위해 선언하였습니다.

 

 

 현재 페이지에 대한 정보인 'Criteria'와 게시물의 총개수인 'total'을 파라미터를 부여한 PageMakerDTO 생성자를 작성합니다. 해당 생성자는 전달받은 Criteria와 total 정보를 활용하여 계산 과정을 거친 후 PageMakerDTO의 변수에 대한 값을 초기화하게 됩니다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 
    /* 생성자 */
    public PageMakerDTO(Criteria cri, int total) {
        
        this.cri = cri;
        this.total = total;
        
        /* 마지막 페이지 */
        this.endPage = (int)(Math.ceil(cri.getPageNum()/10.0))*10;
        /* 시작 페이지 */
        this.startPage = this.endPage - 9;
        
        /* 전체 마지막 페이지 */
        int realEnd = (int)(Math.ceil(total * 1.0/cri.getAmount()));
        
        /* 전체 마지막 페이지(realend)가 화면에 보이는 마지막페이지(endPage)보다 작은 경우, 보이는 페이지(endPage) 값 조정 */
        if(realEnd < this.endPage) {
            this.endPage = realEnd;
        }
        
        /* 시작 페이지(startPage)값이 1보다 큰 경우 true */
        this.prev = this.startPage > 1;
        
        /* 마지막 페이지(endPage)값이 1보다 큰 경우 true */
        this.next = this.endPage < realEnd;
        
        
    }
 

 

그림 4

 

 

 작성한 생성자에선 다음의 순서대로 값들을 계산하여 각 변수를 초기화 하게 됩니다.

 


 "화면에 보일 마지막 페이지" => "화면에 보여질 시작 페이지" => "전체 마지막 페이지 계산" => "화면에 보여질 마지막 페이지 유효한지 체크" => "화면에 보여질 페이지 이전페이지 존재 여부" => "화면에 보여질 페이지 다음페이지 존재 여부"

 

각 단계를 조금 자세히 살펴보겠습니다.

 

 

"화면에 보여질 마지막 페이지"

 

 

 

 화면에 보일 끝 번호를 구하기 위해서 Math.ceil() 함수를 사용할 것입니다. 현재의 페이지를 10으로 나눈 후 그값을 올림하고, 다시 10을 곱해줍니다. 이공식에 의하면 현재 페이지가 7인 경우는 endPage 값이 10, 현재페이지 23인경우 endPage 30이 됩니다. (int)로 다시 형변환 해주는 이유는 Math.ceil()메소드의 반환 타입이 double이기 때문입니다.

 

 

"화면에 보여질 시작 페이지"

 

그림 7

 

그림 8

 

 화면에 표시될 페이지 번호들은 10개이기 때문에 끝번호가 구해졌다면 거기서 9를 빼준다면 첫 번째 번호를 구할 수 있게 됩니다.

 

 

"전체 마지막 페이지 계산"

 

그림 9

 

 현재 게시물의 총개수를 통해 전체 페이지의 마지막 페이지(realEnd) 값을 구하는 식입니다. 전체 페이지(total)를 화면에 표시될 게시물 수(amount)로 나눈 후 올림합니다. 올림에는 Math.ceil() 메서드를 사용합니다.

 total에 1.0을 곱해준 이유는 int형인 total과 int형인 amount를 나눌 경우 본래는 소수점이 나와야 하는 경우에도 실제로는 소수점을 없애버리고 정수만 리턴하기 대문입니다. 따라서 1.0 값을 total에 곱해줌으로써 double 타입으로 형변환 한 후, amount값으로 나눈 결과 또한 형변환되어 값이 소수점으로 출력될 수 있도록 합니다.

 

 

"화면에 보일 마지막 페이지 유효한지 체크"

 

그림 10

 

 위의 코드가 실행되는 시점의 '화면에 표시될 마지막 페이지'인 endPage 값의 경우 무조건 10 단위의 값이 들어 있습니다. 문제는 이럴 경우 실제 '전체 페이지 마지막 번호'인 realEnd 값보다 큰 경우가 발생합니다. 예를 들어 실제 마지막 페이지는 7이 되어야 하지만 화면에 표시되는 마지막 페이지는 10인 경우가 발생합니다.

 이러한 상황을 막기 위해서 IF 조건문을 통하며 endPage가 realEnd보다 큰 경우 endPage에 realend값을 저장하도록 코드가 작성되었습니다.

 

 

"화면에 보일 페이지 이전, 다음 페이지 존재 여부"

 

그림 11

 

그림 12

 

 '이전 페이지 존재 여부'경우 '시작페이지(startPage)'가 1보다 크다면 존재하기 때문에 startPage>1 이면 true가 저장되도록 합니다. 화면에 1부터 10 페이지가 보이는 경우를 제외하면 시작 페이지가 모두 10보다 크기 때문에 그 이전 10개의 번호 페이지로 이동할 수 있는 '이전 페이지 버튼'이 존재해야 합니다.

 '다음 페이지 존재 여부' 경우 '전체 페이지 마지막 번호'(realEnd)보다 '화면에 보이는 마지막 번호(endPage)'가 작다면 존재하기 때문에 endPage <realEnd 이면 true가 저장되도록 합니다. 예를 들어 화면에 보이는 마지막 페이지가 20인데 전체 페이지 마지막 번호가 27이면 '다음 페이지 버튼'이 존재해야 합니다. 반대로 화면에 보이는 마지막 페이지가 23인데 전체 페이지 마지막 번호가 23이면 이동할 수 있는 다음 10개의 페이지가 존재하지 않기 때문에 '다음 페이지 버튼'이 없어야 합니다.

 

 

 

Getter/Setter 메소드, toString 메소드 추가

 

 위의 생성자 작성을 완료하였다면 PageMakerDTO 클래스가 가지고 있는 변수들을 Getter/Setter/toString 메소드를 추가합니다.

 

그림 13

 

그림 14

 

그림 15

 

 

 

2. 게시물 전체 개수 메소드

 순서 1에서 작성한 PageMakerDTO를 활용하기 위해선 Criteria 클래스의 데이터와 게시물 전체 개수 데이터가 필요로합니다. Criteria 클래스는 현재 만들어서 활용하고 이지만 '게시물 전채 개수'에 대한 데이터를 얻는 수단이 없습니다. 따라서 '게시물 전채 개수(total)' 값을 얻을 수 있는 쿼리를 실행하는 Mapper, Service 메소드를 만듭니다. (포스팅에서 작성은 하지 않았지만 Mapper메소드와 Service 메소드가 작동하는지 확인하기 위해 Junit 테스트를 진행하였습니다.)

 

BoardMapper.xml

 

1
2
3
4
5
6
7
8
 
    <!-- 게시물 총 개숫 -->
    <select id="getTotal" resultType="int">
    
        select count(*) from vam_board
    
    </select>
 

 

그림 16

 

BoardMapper.java 인터페이스

 

1
2
3
4
 
    /* 게시판 총 갯수 */
    public int getTotal();
 

 

그림 17

 

BoardSerivec.java 

 

1
2
3
4
 
    /* 게시판 총 갯수 */
    public int getTotal();
 

 

그림 18

 

BoardServiceImpl.java

 

1
2
3
4
5
6
7
8
 
    /* 게시물 총 갯수 */
    @Override
    public int getTotal() {
        
        return mapper.getTotal();
    }    
 

 

그림 19

 

 

 

3. Controller 처리

 'board/list' url 매핑 메소드 구현부를 아래와 같이 수정해줍니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
    /* 게시판 목록 페이지 접속(페이징 적용) */
    @GetMapping("/list")
    public void boardListGET(Model model, Criteria cri) {
        
        log.info("boardListGET");
        
        model.addAttribute("list", bservice.getListPaging(cri));
        
        int total = bservice.getTotal();
        
        PageMakerDTO pageMake = new PageMakerDTO(cri, total);
        
        model.addAttribute("pageMaker", pageMake);
        
    }    
 

 

그림 20

 

 PageMakerDTO 클래스의 데이터를 뷰(View)로 보내기 위해 addAttribute()메소드를 활용하여 "pageMaker"속성에 저장하였습니다. 

 

※변수를 일일이 선언(total, pageMake) 한 것은 이해를 위해입니다. 따라서 파라미터에 바로 메소드와 인스턴스 선언을 작성해도 됩니다.

 

 

 

 

4. View 처리

 서버로부터 넘겨받은 "pageMaker" 속성 데이터를 통해 '페이지 이동 인터페이스'를 구현합니다.

 

구현 순서는 "번호 페이지 구현 " => "다음, 이전 버튼 구현" => "현재 페이지 표시 구현"으로 진행하겠습니다. 

 

시작하기 전 서버에 페이지 이동할 때 필요정보인 pageNum과 amount정보를 전송하기 위해 기존 <from> 태그에 아래의 <input> 태그를 작성합니다

 <input> 태그의 value값은 현재 페이지의 정보가 저장되도록 하였습니다. 이는 현 페이지에서 '조회, 수정 페이지'로 이동하였다가 다시 현 페이지로 이동하기 위해 작성한 것인데 이에 대해 선 다음 포스팅에서 알아봅니다.

 

1
2
3
4
 
        <input type="hidden" name="pageNum" value="${pageMaker.cri.pageNum }">
        <input type="hidden" name="amount" value="${pageMaker.cri.amount }">    
 

 

그림 21

 

"번호 페이지 구현"

 

 <table> 태그가 끝나는 위치에 '페이지 인터페이스'를 작업을 위한 <div> 태그를 작성합니다.

 

1
2
3
4
5
6
7
 
    <div class="pageInfo_wrap" >
        <div class="pageInfo_area">
 
        </div>
    </div>
 

 

그림 22

 

 페이지 이동 버튼은 <ul><li> 태그를 사용하여 구현합니다.  서버로부터 전달받은 "pageMaker"속성에 저장된 startPage, 와 endPage값을 가지고 <c:forEach> 태그를 활용하여 '페이지 번호'를 화면에 출력시킵니다. 각 번호는 <a> 태그로 감쌋으며 href 속성의 값은 '페이지 번호'가 들어가도록 작성합니다. (form태그를 통해 작동시킬 것입니다.) 

 

1
2
3
4
5
6
 
                <!-- 각 번호 페이지 버튼 -->
                <c:forEach var="num" begin="${pageMaker.startPage}" end="${pageMaker.endPage}">
                    <li class="pageInfo_btn"><a href="${num}">${num}</a></li>
                </c:forEach>
 

 

그림 23

 

그림 24

 

 출력시킨 '페이지 이동 번호'가 동작시키기 위해 JS코드 작업을 할 차례입니다. 먼저 페이지 번호(a태그) 클릭하였을 때 동작하는 메소드를 작성합니다.

 

1
2
3
4
5
6
 
    $(".pageInfo a").on("click"function(e){
 
        
    });
 

 

 

메소드 구현부에 아래의 순서대로 동작하도록 코드를 추가합니다.

 


"a태그 동작 멈춤"  =>  "<form> 태그 내부 pageNum과 관련된 <input>태그의 vlaue 속성값을 클릭한 <a> 태그의 페이지 번호를 삽입"  =>   "<form>태그 action 속성 추가 및 '/board/list'을 속성값으로 추가"  
=>   "<form>태그 서버 전송"

 

1
2
3
4
5
6
7
8
9
10
 
    $(".pageInfo a").on("click"function(e){
 
        e.preventDefault();
        moveForm.find("input[name='pageNum']").val($(this).attr("href"));
        moveForm.attr("action""/board/list");
        moveForm.submit();
        
    });
 

 

그림 26

 

 '페이지 이동 번호'를 클릭하여 페이지가 이동되는지 테스트합니다.

 

그림 27

 

그림 28

 

그림 29

 

 

 

"다음 이전 버튼 구현"

 

 서버로부터 전달받은 "pageMaker"속성의 Bollean타입의 "prev, next"값을 활용하여 버튼을 출력되도록 아래와 같이 작성하였습니다. <c:if> 태그를 활용하여 "pageMaker.prev, pageMaker.next"값이 true일 때 버튼이 보이도록 합니다.

 

 '이전, 다음 버튼' <a>태그 href속성 값을 'startPgae-1', 'endPage+1'을 삽입하였습니다. 이는 현재 보이는 10개의 페이지 번호 그 이전 혹은 다음으로 이동하기 위함입니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
 
                <!-- 이전페이지 버튼 -->
                <c:if test="${pageMaker.prev}">
                    <li class="pageInfo_btn previous"><a href="${pageMaker.startPage-1}">Previous</a></li>
                </c:if>
                
                
                <!-- 다음페이지 버튼 -->
                <c:if test="${pageMaker.next}">
                    <li class="pageInfo_btn next"><a href="${pageMaker.endPage + 1 }">Next</a></li>
                </c:if>    
 

 

그림 30

 

 

 '페이지 이동 인터페이스'를 보기가 좀 편하도록 CSS 설정을 추가하였습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
  .pageInfo{
      list-style : none;
      display: inline-block;
    margin: 50px 0 0 100px;      
  }
  .pageInfo li{
      float: left;
    font-size: 20px;
    margin-left: 18px;
    padding: 7px;
    font-weight: 500;
  }
 a:link {color:black; text-decoration: none;}
 a:visited {color:black; text-decoration: none;}
 a:hover {color:black; text-decoration: underline;}
 

 

그림 32

 

 

 앞서 버튼이 작성한 JS코드를 통해 '이전, 다음 버튼'도 동작합니다. 따라서 해당 버튼이 잘 동작하는지 테스트합니다.

 

그림 34

 

그림 35

 

그림 36

 

 

"현재 페이지 표시 구현"

 

 'pageMaker.cri.pageNum(현재 페이지)'와 3항 연산자를 활용하여 '현재 페이지' <a> 태그의 class 속성 값에 "active"문자를 추가 할것입니다. 그리고 변경된 <a>태그의 class 속성값을 활용하여 css 설정을 줘서 '현재 페이지' 버튼에 배경색을 추가되도록 할 것입니다.

 

 페이지 번호 <li> 태그를 아래와 같이 변경합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
    <!-- 기존 -->
 
                <!-- 다음페이지 버튼 -->
                <c:if test="${pageMaker.next}">
                    <li class="pageInfo_btn next"><a href="${pageMaker.endPage + 1 }">Next</a></li>
                </c:if>    
 
    <!-- 변경 후 -->
 
                <!-- 각 번호 페이지 버튼 -->
                <c:forEach var="num" begin="${pageMaker.startPage}" end="${pageMaker.endPage}">
                    <li class="pageInfo_btn ${pageMaker.cri.pageNum == num ? "active":"" }"><a href="${num}">${num}</a></li>
                </c:forEach>

 

그림 37

 

 현재 페이지 <a> 태그의 class 속성 값이 변경되었는지 확인합니다.

 

그림 38

 

그림 39

 

 현재 페이지 버튼이 동작하도록 css 설정을 추가합니다.

 

1
2
3
4
5
 
  .active{
      background-color: #cdd5ec;
  }
 

 

그림 40

 

그림 41

 

 

REFERENCE

 

 

DATE

  • 2020.03.01
728x90
반응형
Comments