최근 국비교육이 끝났다.. 이제 취업만 남았는데.. 이력서를 쓰면서 그동안 못썼던 포스팅도 계속해보려고 한다.
우선 개인프로젝트가 끝나고 팀 프로젝트로 들어가는 바람에 임시저장만 해둔 글부터 차근차근 써보려고 한다.
아래 내용은 개인 프로젝트 진행 시, 사용했던 무한 스크롤 구현한 코드를 정리한 내용이다.
우리가 웹사이트를 이용하다보면 여러 가지 페이징 기법이 적용된 걸 볼 수 있다. 일반적인 게시판 같은 경우 하단에 번호가 있고 해당 번호를 클릭하거나 다음 버튼을 누르면 다음 페이지가 나타난다. 하지만 또 다른 페이징 기법인 마우스 스크롤을 내리면 그다음 페이지가 나타나는 걸 확인할 수 있다.
1. 기본 페이징 기법 vs 무한 스크롤
1-1 기본 페이징 기법
기본적인 페이징 기법은 아래와 같이 페이지네이션 처리한 것을 말한다. 일반적이 게시판이나 커뮤니티 사이트에서 많이 사용하는 방법이다. 아래 이미지와 같이 노출하고 싶은 개수의 게시물은 설정하고 나머지 부분은 페이지 처리하는 것이다.
1-2. 무한 스크롤
아래는 무한 스크롤이 적용된 화면이다. 온라인에서 각종 서비스를 이용하다 보면 한번씩 경험해 봤을 것이다. 스크롤을 내리면 자동으로 다음 게시물들이 로드되어 노출되는 기능이다.
2. 프로세스 흐름 & 코드 구현
2-1. 프로세스 흐름
1) 메인페이지에 접속하면 MainController에서 전달받은 데이터를 출력한다.
2) 스크롤이 맨끝으로 내려가면 AJAX통신을 통해 AjaxlistController에서 전달받은 데이터를 메인 페이지에 추가한다.
3) 더 이상 불러올 데이터가 없다면 AJAX 통신을 종료한다.
위와 같은 프로세스로 구현하면 된다. jsp파일과 controller 파일의 코드를 살펴보자.
(Dao파일과 Mapper파일을 기재하지 않았습니다.자세한 코드 내용은 아래 깃허브에서 확인 가능합니다.)
https://github.com/namhyun99/jsp-servlet-project
2-2. main.jsp 만들기
우선 메인페이지 부분에서 리스트를 출력하는 코드 부분이다.
<!--contents-->
<div class="flex1" style="display: flex">
<div class="flex" style="flex: 1 1 auto">
<section id="list-wrap">
<c:forEach var="dto" items="${list}">
<div class="items">
<a href="./board/view?c_idx=${dto.c_idx}">
<div class="thumb_img">
<c:choose>
<c:when test="${dto.filesize > 0}">
<img src="/thumbnail/${dto.filename}">
</c:when>
<c:otherwise>
<img src="./resources/asset/images/no_thumb.png">
</c:otherwise>
</c:choose>
</div>
</a>
<div class="text-wrap">
<a href="./board/view?c_idx=${dto.c_idx}">
<div class="contents">
<h3>${dto.subject}</h3>
<div class="desc">
<p>${dto.content}</p>
</div>
</div>
</a>
<div class="sub-info">
<span> <fmt:formatDate value="${dto.write_date}"
pattern="yyyy년 M월 d일" />
</span> <span> · ${dto.cmt_count}개의 댓글</span>
</div>
<div class="user-info">
<div class="profile">
<c:choose>
<c:when test="${dto.profile_img != '-'}">
<img src="/profile/${dto.profile_img}">
</c:when>
<c:otherwise>
<img src="./resources/asset/images/no_profile.png">
</c:otherwise>
</c:choose>
by.<span>${dto.userid}</span>
</div>
<div class="view-count">
<i class="fas fa-eye"></i><span>${dto.view_cnt}</span>
</div>
</div>
</div>
</div>
</c:forEach>
</section>
</div>
</div>
2-3. JavaScript 작성하기
스크롤 이벤트 처리 부분과 리스트를 가져오는 함수를 아래와 같이 작성해주면 된다.
var curPage = 1; //페이지 초기값
var isLoading = false; //현재 페이지가 로딩중인지 여부
$(window).on("scroll", function() {
var scrollTop = $(window).scrollTop(); // 위로 스크롤된 길이
var windowsHeight = $(window).height(); //웹브라우저의 창의 높이
var documentHeight = $(document).height(); // 문서 전체의 높이
var isBottom = scrollTop + windowsHeight + 10 >= documentHeight; //바닥에 갔는지 여부
if (isBottom) {
//만일 현재 마지막 페이지라면
if (curPage >= ${totPage}) {
return false; //함수종료
} else {
isLoading = true; //위에서 종료되지 않으면 로딩상태를 true로 변경
$("#load").show(); //로딩바 표시
curPage++; //현재페이지 1증가
getList(curPage); //추가로 받을 리스트 ajax처리
}
}
});
// 리스트 불러오기 함수
function getList(curPage) {
$.ajax({
type: "get",
url : "${path}/ajax_page.do",
data : { "curPage" : curPage,
"order" : "view_cnt"},
success:function(data){
//서버에서 전송된 데이터를 list-wrap에 추가하기
$("#list-wrap").append(data);
$("#load").hide(); //로딩바 숨기기
isLoading = false; // 로딩여부를 false로 변경
}
});
}
2-4. Controller 작성
아래 코드는 AjaxlistController.java 코드이다. MainController와 거의 똑같지만, 포워딩하는 페이지가 다르다.
2-3 자바스크립트 코드에서 getList함수에서 ajax호출이 성공하면 여기서 포워딩한 페이지를 그대로 보내준다. 그렇기 때문에 컨트롤러 작성 후, jsp페이지를 하나 더 만들어 주어야 한다.
아래 코드에서 keyword와 order 두 개의 파라미터 값이 있는 이유는 해당 컨트롤러는 검색페이지 영역에서도 활용했기 때문이다. 메인 페이지를 구현할 때는 필요 없는 부분이다. 페이지 처리 부분은 따로 작성된 Pager.java을 활용하여 구현하였다.
package controller;
import java.io.IOException;
import java.util.List;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.BoardDAO;
import dto.ContentsDTO;
import util.Pager;
@WebServlet("/ajax_page.do")
public class AjaxListController extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
BoardDAO dao = new BoardDAO();
//넘어온 파라미터값 확인
String keyword = request.getParameter("keyword");
String order =request.getParameter("order");
//파라미터값 null 처리
if(keyword == null) keyword = "";
if(order == null) order = "";
//페이지 번호 초기값 설정
int curPage = 1;
//만약 넘어온 페이지번호가 null값이 아니라면..
if (request.getParameter("curPage") != null) {
//받아온 값을 curPage에 저장
curPage = Integer.parseInt(request.getParameter("curPage"));
}
//검색 갯수를 조회
int count = dao.getSearchCount(keyword);
//페이지 새로운 객체 생성
Pager pager = new Pager(count, curPage);
//게시물 시작
int start = pager.getPageBegin();
//게시물 끝
int end = pager.getPageEnd();
//페이지 전체 갯수
int totPage = pager.getTotPage();
//리스트 불러오기
List<ContentsDTO> list = dao.getSearchList(start, end, order, keyword); //검색리스트
//포워딩한 페이지로 데이터 전달
request.setAttribute("list", list);
request.setAttribute("page", pager);
request.setAttribute("totPage", totPage);
request.setAttribute("count", count);
request.setAttribute("keyword", keyword);
//포워딩 페이지
String page = "/ajax_list.jsp";
RequestDispatcher rd = request.getRequestDispatcher(page);
rd.forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
2-5. ajax_list.jsp 만들기
이 부분은 메인페이지 리스트를 출력하는 코드와 동일하다.
<c:forEach var="dto" items="${list}">
<div class="items">
<a href="./board/view?c_idx=${dto.c_idx}">
<div class="thumb_img">
<c:choose>
<c:when test="${dto.filesize > 0}">
<img src="/thumbnail/${dto.filename}">
</c:when>
<c:otherwise>
<img src="./resources/asset/images/no_thumb.png">
</c:otherwise>
</c:choose>
</div>
</a>
<div class="text-wrap">
<a href="./board/view?c_idx=${dto.c_idx}">
<div class="contents">
<h3>${dto.subject}</h3>
<!-- <div class="desc">
<p>${dto.content}</p>
</div> -->
</div>
</a>
<div class="sub-info">
<span> <fmt:formatDate value="${dto.write_date}"
pattern="yyyy년 M월 d일" />
</span> <span> · ${dto.cmt_count}개의 댓글</span>
</div>
<div class="user-info">
<div class="profile">
<c:choose>
<c:when test="${dto.profile_img != '-'}">
<img src="/profile/${dto.profile_img}">
</c:when>
<c:otherwise>
<img src="./resources/asset/images/no_profile.png">
</c:otherwise>
</c:choose>
by.<span>${dto.userid}</span>
</div>
<div class="view-count">
<i class="fas fa-eye"></i><span>${dto.view_cnt}</span>
</div>
</div>
</div>
</div>
</c:forEach>
3. 마치며..
위와 같이 코드를 작성하면 무한 스크롤 구현이 가능하다. 하지만 여기서 궁금한 점이 있었다. 꼭 jsp파일을 따로 만들어서 포워딩 처리해야 할까? 다른 방법은 없을까?라는 궁금증이 너무 많았다.
그러다가 찾아낸 방법은 처음부터 메인페이지 접속 시, 리스트를 AJAX통신으로 받고 그 결과를 로딩할 때 노출하면 되지 않을까 생각했다. 그리고 Controller단에서 DB에서 조회한 리스트를 JSON형태로 뿌려주고, 해당 데이터를 가지고 jQuery의 each함수로 html을 생성해주면 될 것 같다.
이 부분은 나중에 시도해보고 포스팅 하도록 하겠습니다! 혹시 더 좋은 방법이 있거나 다른 방법이 있다면 알려주세요!