ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [3-ch14 Spring 게시판] view, 페이징의 화면 처리
    Back-End/Spring Legacy 2022. 8. 29. 12:35

     

    URL 파라미터를 이용해 정상적으로 원하는 페이로 이동하는 것을 확인했다면, 화면 밑에 페이지 번호를 표시하고 사용자가 페이지 번호를 클릭하면 이동하도록 처리한다.

     

    이번 포스팅은 아래와 같은 과정을 따른다.

     

    1. 브라우저 주소창에 페이지 번호를 전달해서 결과를 확인하는 단계
    2. JSP에서 페이지 번호를 출력하는 단계
    3. 각 페이지 번호에 클릭 이벤트 처리
    4. 전체 데이터 개수를 반영해서 페이지 번호 조절

     

     

    페이징 처리를 하기 위해선 현재 페이지 번호 (page), 이전 과 다음으로 이동 가능한 링크의 표시 여부(prev, next), 화면에서 보여지는 페이지의 시작 번호와 끝 번호(startPage, endPage) 와 같은 정보들이 필요하다. 

     

     

     

    끝 페이지 번호와 시작 페이지 번호

     

    페이징 처리를 하기 위해서 우선적으로 필요한 정보는 현재 사용자가 보고 있는 페이지(page)의 정보이다. 예를 들어, 사용자가 5페이지를 본다면 화면의 페이지 번호는 1부터 시작하지만, 19페이지를 본다면 11부터 시작해야하기 때문이다. (만약 화면에 10페이지 번호를 출력한다고 가정할 때) 

     

     

    페이징의 끝 번호(endPage) 계산 - 페이지 번호가 10개씩 보인다고 가정

    (Math.ceil : 소수점 이하를 올림)

     

    this.endPage = (int)(Math.ceil(페이지번호 /10.0)) * 10; 

     

    1페이지의 경우 : Math.ceil(1 / 10.0 ) * 10 =  Math.ceil(0.1 ) * 10 = 10

    10페이지의 경우 : Math.ceil(10 / 10.0 ) * 10 = Math.ceil(1) * 10  = 10

    11페이지의 경우 : Math.ceil(11 / 10.0 ) * 10 =  Math.ceil(1.1 ) * 10  = 20

     

    끝 번호(endPage)는 아직 개선이 필요하다. why? 만약 전체 데이터 수가 10보다 적다면 예를들어 3페이지라면 10페이지로 끝나면 안되기 때문이다. 그럼에도 endPage를 먼저 계산한 이유는 시작 페이지 startPage를 계산하기 수월하기 때문이다. 

     

    만일 화면에 10개씩 보여준다면 시작 번호(startPage)는 끝번호에서 9라는 값을 뺀 값이 된다. 

     

     

    페이징의 시작 번호 (startPage) 계산

    this.startPage = this.endPage - 9;

     

    끝 번호는 전체 데이터(total)에 의해 영향을 받는다. 예를 들어 10개씩 보여주는 경우 전체 데이터 수가 80개라고 가정하면 끝 번호(endPage)는 10이 아닌 8이 되어야 한다. 

     

    만일 끝 번호(endPage)와 한 페이지당 출력되는 데이터 수(amount)의 곱이 전체 데이터 수(total)보다 크다면 끝 번호(endPage)는 다시 total을 이용해서 계산되어야 한다. 

     

     

    total을 통한 endPage의 재계산

    realEnd = (int)(Math.ceil((total/1.0) / amount ) ) ; 

    if(realEnd < this.endPage) {
     this.endPage = this.realEnd;
    }

     

    먼저 전체 데이터 수(total)를 이용해서 진짜 끝 페이지(realEnd)가 몇 번까지 되는지를 계산한다. 

    예를들어 전체 페이지가 88페이지라면 Math.ceil(88/1.0) 이 값을 amount(화면에 보여지는 페이지 개수 , 10) = 88.0을 amount(10)으로 나눈다. 그럼 8.8 이걸 Math.ceil(8.8) 하면 realEnd = 9

     

    그리고 if문으로 만약 realEnd가 구해둔 endPage보다 작으면 끝 번호는 작은 값이 되어야만 한다. 

     

     

     

    이전(prev)과 다음(next)

     

    이전과 다음은 아주 간단히 구할 수 있다.

     

    이전(prev)의 경우 시작 번호(startPage)가 1보다 큰 경우라면 존재한다. 

    this.prev = this.startPage > 1;

     

    다음(next)으로 가는 링크의 경우 위의 realEnd가 끝 번호(endPage)보다 큰 경우에만 존재한다. 

    this.next = this.endPage < realEnd;

     

     

     

     

    페이징 처리를 위한 클래스 설계

     

    화면에 페이징 처리를 위해 여러 정보가 필요하다면 클래스를 구성해 처리하는 방식도 좋다. 클래스를 구성하면 Cotroller 계층에서 JSP 화면에 전달할 때에도 객체를 생성해서 Model에 담아 보내는 과정이 단순해지는 장점이 있다. 

     

     

    <org.zerock.domain.pageDTO>

    package org.zerock.domain;
    
    import lombok.Getter;
    import lombok.ToString;
    
    @Getter
    @ToString
    public class PageDTO {
    	
    	private int startPage;
    	private int endPage;
    	private boolean prev, next;
    	
    	private int total;
    	private Criteria cri;
    	
    	public PageDTO(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;
    		
    		//realEnd의 값을 아래 주석과 같이 처리해서 마지막페이지 -1 만큼만 조회됐다. 괄호 조심! 
    		//(int)((Math.ceil(total*1.0)/cri.getAmount()));
    		int realEnd = (int)(Math.ceil((total*1.0)/cri.getAmount()));
    		
    		//this.endPage = realEnd <= endPage? realEnd :endPage;	
    			
    		if(realEnd<this.endPage) {
    			this.endPage = realEnd;
    		}
    		
    		this.prev = this.startPage >1;
    		this.next = this.endPage <realEnd;
    	}
    }

     

     

    PageDTO는 생성자를 정의하고 Criteria와 전체 데이터 수(total)를 파라미터로 지정한다. Criteria 안에는 페이지에서 보여주는 데이터 수(amount)와 현재 페이지 번호(pageNum)를 가지고 있기 때문에 이를 이용해서 필요한 모든 내용을 설계할 수 있다. BoardController에서는 pageDTO를 사용할 수 있도록 Model에 담에서 화면에 전달해줄 필요가 있다.

     

     

     

    <BoardController>

    @GetMapping("/list")
    public void list(Model model, Criteria cri) {
      log.info("list");
      model.addAttribute("list", service.getList(cri));
      //model.addAttribute("pageMaker", new PageDTO(cri, 123));
    
      int total = service.getTotal(cri);
      log.info("total : " +total);
      model.addAttribute("pageMaker", new PageDTO(cri, total));
    }

     

     

     

    JSP에서 페이지 번호 출력, 이벤트 처리

     

     

    JSP에서 페이지 번호를 출력하는 부분은 JSTL을 이용해서 처리할 수 있다. table 태그가 끝난 직후 페이지 처리를 해준다. 

     

    <!-- 페이징 처리 -->
    <div class='pull-right'>
        <ul class="pagination">
    
            <c:if test="${pageMaker.prev}">
              <li class="paginate_button previous">
                <a href="${pageMaker.startPage-1 }">Previous</a>
              </li>
            </c:if>
    
            <c:forEach var="num" begin="${pageMaker.startPage}"
              end="${pageMaker.endPage}">
            <li class="paginate_button  ${pageMaker.cri.pageNum == num ? "active":""} ">
                <a href="${num }">${num}</a></li>
            </c:forEach>
            <%-- c:if test는 항상 boolean 값을 반환하기에 여기서 계산하려 하지 말고
                    불린형 값만 나오게 코드 짜는 게 좋다. --%>
            <c:if test="${pageMaker.next}">
              <li class="paginate_button next">
                <a href="${pageMaker.endPage+1 }">Next</a></li>
            </c:if>
    
        </ul>
    </div>

     

     

     

    <li class="paginate_button  ${pageMaker.cri.pageNum == num ? "active":""} "> 는 현재 페이지를 활성화 시키는 즉) 현재 페이지 번호에 눈에 띄게 색을 칠해주는 active 해주는 버튼의 속성값인데 만약) pageMaker.cri.pageNum이 현재 페이지랑 같다면? "active" 활성화시키고 아니라면 그냥 속성값을 따로 주지 않는다. 

     

    이렇게 코드를 짰다면 예를 들어, total이 123이라는 숫자로 지정되어 있다고 가정할 때 5페이지를 조회하면 next값은 true가 되어야 하고 반면, amount 값이 20인 경우 7페이지만 출력 되어야 한다. 

     

     

     

    num = 2 & amount = 10

     

    num = 4 & amount = 20

     

     

     

    화면에서 페이지 번호가 보이고 그 번호를 눌렀을 때 이벤트 처리가 남아있다. 일반적으로 <a> 태그의 href 속성을 이용하는 방법을 사용할 수도 있지만, 직접 링크를 처리하는 방식의 경우 검색 조건이 붙고 난 후에 처리가 복잡해지므로( 파라미터가 많아지면 처리 어려워짐  ex)   /board/list?num=5&amount=8&keyword=서비스 ,,,, ,,,,,,)  JavaScript를 통해 처리하는 방식을 이용한다. <a> 태그의 href 속성값으로 페이지 번호를 가지도록 한다.  

     

     

    <a href="${pageMaker.startPage-1 }">Previous</a>
    <a href="${num }">${num}</a></li>
    <a href="${pageMaker.endPage+1 }">Next</a></li>

     

     

    이렇게만 지정해두면 링크로 페이지 번호가 넘어간다. 이 상태에서 번호를 클릭하면 url이 존재하지 않기 때문에 문제가 생긴다. <a> 태그가 원래 동작을 하지 못하도록 JavaScript 처리를 해주고, 실제 페이지를 클릭하면 동작하는 부분은 별도의 <form> 태그를 이용해서 처리하도록 한다.

     

     

    <list.jsp>

     

     

    <form id='actionForm' action="/board/list" method='get'>
        <input type='hidden' name='pageNum' value='${pageMaker.cri.pageNum}'>
        <input type='hidden' name='amount' value='${pageMaker.cri.amount}'>
    </form>

     

     

    <list.jsp script 처리>

     

    var actionForm = $("#actionForm");
    
        $(".paginate_button a").on(
                "click",
                function(e) {
    
                    e.preventDefault();
    
                    console.log('click');
    
                    actionForm.find("input[name='pageNum']")
                            .val($(this).attr("href"));
                    actionForm.submit(); //submit해야 실제 url 이동이 가능하다.
        });

     

     

    list.jsp에서 현재 id 값이 actionForm인  <form> 태그로 url의 이동을 처리하도록 변경했다.  JavaScript에서는 <a> 태그를 클릭해도 페이지 이동이 없도록 preventDefault() 처리를 하고, <form> 태그 내 pageNum 값은 href 속성값으로 변경한다. 이렇게 처리후 페이지 번호를 클릭했을 때  <form> 태그 내 페이지 번호가 바뀌는 것을 확인 가능하다. 

     

     

     

     

    조회 페이지로 이동 시 현재 페이지 번호 기억

     

    목록 화면에서 페이지 번호를 클릭하면 정상적으로 원하는 페이지를 볼 수 있지만, 만약 사용자가 3페이지에 있다가 어떤 게시글의 상세보기를 클릭한다. 그 후 list 버튼으로 목록보기를 누르면 다시 1페이지로 돌아가는 증상이 일어난다. 

     

     

     

     

     

     

    원래 있던 페이지 번호를 기억하려면  현재 목록 페이지의 pageNum과 amount를 같이 전달해야 한다. 이런 경우 페이지 이동에서 사용했던 <form> 태그에 추가로 게시물의 번호를 같이 전송하고, action 값을 조정할 수 있다. 

     

     

    list.jsp에서 게시글 title인 <a> 태그에 href 값을 게시글 번호만 가져오도록 수정한다. (원래는 클릭 시 이동하는 링크 처리) 

     

    <td>${board.bno}</td>
    <td><a class ='move' href='<c:out value="${board.bno }"/>'>
    <c:out value="${board.title}" /></a></td>

     

     

    JavaScript 처리를 위해 a태그에 class 값으로 move를 줬다. 

    move라는 클래스를 가진 태그를 클릭했을 때 원래 태그의 기능은 멈추고, actionForm 태그에다가 hidden 타입의 이름은 name이고 값은 현재 사용자가 클릭한 값을 가지는 input 태그를 추가해주고, actionForm의 action값을 /board/get으로 변경하겠다. 즉) /board/get.jsp 로 이동하는데 여기다가 파라미터 값으로 bno 값은 물론 pageNum과 amount 값도 넘기겠따. 

     

    $(".move").on("click",function(e){
    
        e.preventDefault();
        actionForm.append("<input type='hidden' name='bno' value='"+$(this).attr("href")+"'>");
        //actionForm.attr("action", "/board/get").submit();  아래 두문장 이렇게 적어도 된다. 체이닝! 	
        actionForm.attr("action", "/board/get");
        actionForm.submit(); 
    });

     

     

     

     

    조회 페이지에서 목록 페이지로 이동 시 페이지 번호 유지

     

     

    조회 페이지에서 다시 목록 페이지로 이동하기 위한 파라미터 들이 같이 전송 되었다면 조회 페이지에서 목록으로 이동하기 위한 이벤트 처리를 같이 해야한다. BoardController의 get() 메서드는 원래 게시물의 번호만 받도록 되어있었지만, 추가적인 파라미터인 Criteria를 추가해서 전달받는다.

     

    @GetMapping({"/get","/modify"})
    public void get(@RequestParam("bno") Long bno, Model model, @ModelAttribute("cri") Criteria cri) {
      log.info("get" );
      model.addAttribute("board", service.get(bno));
    }

     

     

    @ModelAttribute는 자동으로 Model에 데이터를 지정한 이름으로 담아준다. 

     

     

     

    <get.jsp>

    <button data-oper='modify' class="btn btn-default">Modify</button>
    <button data-oper='list' class="btn btn-info">List</button>
    <form id='operForm' action="/board/modify" method="get">
        <input type='hidden' id='bno' name='bno' value=${board.bno } />
        <input type='hidden' id='pageNum' name='pageNum' value=${cri.pageNum } />
        <input type='hidden' id='amount' name='amount' value=${cri.amount } />
    </form>

     

    operForm이라는 id를 가진 form태그에서 cri라는 이름으로 전달된 Criteria 객체를 이용해 pageNum과 amount 값을 태그로 구성하고, 버튼을 클릭했을 때 정상적으로 목록 페이지로 이동하게 한다. 

     

     

    <get.jsp script 처리>

    <script type="text/javascript">
    $(document).ready(function(){
    	
    	var operForm = $("#operForm"); //id가 operForm인 태그 값을 가져온다. 
    	
    	$("button[data-oper='modify']").on("click",function(e){
    		operForm.attr("action","/board/modify").submit();
    	});
    	
    	$("button[data-oper='list']").on("click",function(e){
    		operForm.find("#bno").remove();
    		operForm.attr("action","/board/list");
    		operForm.submit();
    	});
    	
    });
    </script>

     

     

     

     

    수정과 삭제 처리 후 페이지 기억

     

    modify.jsp에는 <form> 태그 이용해 데이터를 처리한다. get.jsp랑 비슷한 방식!  따라서 Criteria를 Model에서 사용하기 때문에 아래와 같이 태그를 만들어서 <form> 태그 전송에 포함한다. 

     

    <input type='hidden' id='pageNum' name='pageNum' value=${cri.pageNum } />
    <input type='hidden' id='amount' name='amount' value=${cri.amount } />

     

     

    POST 방식으로 진행하는 수정과 삭제 처리에서 처리 완료 후 현재 있던 페이지로 이동하기 위해선 Controller에 값을 추가해줘야 한다. 

     

    @PostMapping("/modify")
    public String modify(BoardVO board,RedirectAttributes rttr , @ModelAttribute("cri") Criteria cri) {
      log.info("modify.....");
      if(service.modify(board)) {
         rttr.addFlashAttribute("result", "success");
      }
    
      rttr.addAttribute("pageNum", cri.getPageNum());
      rttr.addAttribute("amount", cri.getAmount());
    
    
      return "redirect:/board/list";
    }

     

     

    만약 rttr.addAttribute에 값을 지정해주지 않았다면 수정 완료 후 pageNum을 기억하지 못해 다시 1페이지인 /board/list로 돌아가게 된다.

     

     

     

    그러나 rttr.addAttribute로 pageNum과 amount를 넘겨주면 수정/삭제 후 현재 있던 페이지로 이동하게 된다.

     

     

     

     

     

     

    수정/삭제 페이지에서 목록 페이지 이동

     

    예를들어 게시글을 수정/삭제하려고 페이지를 이동했는데 그 페이지에서 수정/삭제 완료하지 않고 다시 목록 페이지로 돌아가려고 한다. 그럴때 원래 있던 페이지로 돌아가려면 modify.jsp에 javascript 코드를 수정해주면 된다.

     

    <script type="text/javascript">
    	$(document).ready(function(){
    		
    		var formObj = $("form");
    		
    		$('button').on("click",function(e){
    			//기본동작을 막는다. 버튼을 누르면 해야하는 일 (ex. form 태그 안에서 버튼을 누르면 submit)을 막아준다.
    			e.preventDefault();
    			//$(this) = button을 의미한다.
    			var operation =$(this).data("oper");
    			
    			console.log(operation);
    			
    			if(operation=='remove'){
    				formObj.attr("action", "/board/remove");
    			}else if(operation =='list'){
    				/* self.location ="/board/list";
    				return; */
    				formObj.attr("action","/board/list").attr("method","get");
    				var pageNumTag = $("input[name='pageNum']").clone(); //clone : 내용 복사 후 보관
    				var amountTag = $("input[name='amount']").clone();
    				
    				formObj.empty();
    				formObj.append(pageNumTag);
    				formObj.append(amountTag);
    			}
    			formObj.submit();
    		});
    	});
    </script>

     

     

    목록으로 돌아갈 때 pageNum과 amount를 기억하기 위해서 잠시 clone()으로 값을 기억했다가 formObj에 append로 붙여준다. 게시글 번호나 다른 정보는 모두 지워버린다. (empty() ) 

    반응형

    댓글

Designed by Tistory.