[JSP] 커스텀 태그 예시 - 쪽 번호 매기기

필수 라이브러리

  • jsp-api-x.x.jar
  • servlet-api-x.x.jar
  • el-api-x.x.jar

커스텀 태그 설정에 필요한 소스의 일부만 제공하니 참고만 할 것.

jsp 페이지에서 공통으로 사용될 페이징 처리를 커스텀 태그로 구현한 소스다. 처리 결과는 위 사진과 같다. 먼저 아래 설정 목록을 모두 세팅한 뒤 jsp와 controller에 각각 다음의 코드를 작성한다.

붙여넣기 전에… 아래의 소스대로라면 <<는 first(최초의 페이지 인덱스)를, >>는 last(마지막 페이지 인덱스)를 불러오게 되며 <, >는 각각 바로 이전 페이지, 바로 다음 페이지로의 이동을 의미한다. (딱 1페이지씩만 이동하며 indexPerPage로 나누어 떨어지는 페이지, 혹은 나누어 1만 남는 페이지가 current일 땐 페이지 인덱스가 증가하거나 감소할 것이다.)

만약 페이지 이동 인디케이터인 <, > 를 바로 이전, 다음 페이지로의 이동이 아니라 indexPerPage(한 화면에 표시할 최대 페이지 인덱스 수)만큼의 이동이라면 당연히 계산식을 수정해야한다. (indexPerPage가 10이면 <, >의 의미가 이전 10개의 페이지, 다음 10개의 페이지이길 원할 때)

실제로 자료가 많으면 많을 수록 한 페이지 씩 이동하는것은 탐색 기능으로써의 의미가 퇴색된다. 게다가 넘버링 된 숫자를 클릭하는 것과 중복되는 처리이기도 하고…

페이지 이동을 submission이 아닌 ajax로 처리해야 할 경우 다음 코드로 클릭 이벤트 핸들러를 바꿔준다:

$('.paging').find('a').click(function (event) {
  event.preventDefault(); // location.href 작동방지

  if ($(this).attr("href") != "#") {
    $.ajax({
      url: $(this).attr("href"),
      dataType: 'html',
      success: function (data) {
        $('#resultArea').html(data);
      }
    });
  }
});

URL을 변경해야 한다면 다음 두 방법 중 하나를 이용한다:

$('.paging a').click(function (event) {
  event.preventDefault();

  if ($(this).attr("href") != "#") {
    var hrefURI = $(this).attr('href');
    var params = hrefURI.substring(hrefURI.indexOf('?'));

    $.ajax({
      url: '/test/sampleurl.do' + params,
      type: 'post',
      dataType: 'html',
      success: function (data) {
        $('#resultArea').html(data);
      }
    });
  }
});
$('.paging a').each(function () {
  if ($(this).attr('href') != '#') {
    var hrefURI = $(this).attr('href');
    var params = hrefURI.substring(hrefURI.indexOf('?'));

    $(this).click(function (event) {
      event.preventDefault();
      $.ajax({
        url: '/sampleurl.do' + params,
        type: 'post',
        dataType: 'html',
        success: function (data) {
          $('#resultArea').html(data);
        }
      });
    });
  }
});

jsp

<%@ include file="/scripts/include/paging.jsp"%>

controller

public class SomeController {
    @RequestMapping("/list")
    public ModelAndView drawList(HttpServletRequest request,
            HttpServletResponse response) {

        String currentPage = request.getParameter("currentPage");

        // ... 페이지 관련 수치 계산, 데이터 조회

        request.setAttribute("dataList", [조회한 데이터]);
        request.setAttribute("currentPage", [현재 페이지]);
        request.setAttribute("totalRecordCount", [데이터의  개수]);
        request.setAttribute("recordsPerPage", [페이지  표시할 데이터 ]);
        //request.addAttribute("indexPerPage", 10);
        // 최대 페이지 인덱스 수 (default: 10)
        // pagingTag.java에서 10으로 설정되어 있다.

        // ... 출력
    }
}

currentPage: 페이지 인덱스를 클릭했을 때 컨트롤러에 넘어가는 '요청 페이지 인덱스'의 파라미터명

나머지 소스는 아래와 같으며 의존관계는 대략 jsp -> web.xml -> tld -> java 정도다.

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">

  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>

  <jsp-config>
      <taglib>
          <taglib-uri>/tlds/custom-taglib</taglib-uri>
        <taglib-location>/WEB-INF/tlds/custom-taglib.tld</taglib-location>
      </taglib>
  </jsp-config>
</web-app>

/WEB-INF/tlds/custom-taglib.tld

<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2eeweb-jsptaglibrary_2_0.xsd" version="2.0">
    <tlib-version>1.2</tlib-version>
    <jsp-version>2.1</jsp-version>
    <short-name>Custom Tag Library</short-name>
    <uri>/tlds/custom-taglib</uri>
    <tag>
        <name>paging</name>
        <tag-class>com.sample.PagingTag</tag-class>
        <attribute>
            <name>name</name>
            <required>true</required>
            <rtexprvalue>false</rtexprvalue>
        </attribute>
        <attribute>
            <name>totalRecordCount</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
        <attribute>
            <name>recordsPerPage</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
        <attribute>
            <name>currentPage</name>
            <required>false</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
        <attribute>
            <name>indexPerPage</name>
            <required>false</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
    </tag>
</taglib>

com.sample.PagingTag.java

package com.sample;

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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@SuppressWarnings("serial")
public class PagingTag extends TagSupport {
    private static final Logger logger = LoggerFactory.getLogger(PagingTag.class);

    private String name = null;
    private String href = null;
    private int totalRecordCount = 0;
    private int recordsPerPage = 0;
    private int currentPage = 1;
    private int startIndex = 0;
    private int endIndex = 0;
    private int lastIndex = 0;
    private List<Page> pages = new ArrayList<Page>();
    private Page firstPage = null;
    private Page lastPage = null;
    private Page previousPage = null;
    private Page nextPage = null;
    private int indexPerPage = 10;

    public String getName() {
        return name;
    }

    public int getTotalRecordCount() {
        return totalRecordCount;
    }

    public int getRecordsPerPage() {
        return recordsPerPage;
    }

    public int getIndexPerPage() {
        return indexPerPage;
    }

    public int getCurrentPage() {
        return currentPage;
    }

    public Page getFirstPage() {
        return firstPage;
    }

    public Page getLastPage() {
        return lastPage;
    }

    public Page getPreviousPage() {
        return previousPage;
    }

    public Page getNextPage() {
        return nextPage;
    }

    public List<Page> getPages() {
        return pages;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setTotalRecordCount(int totalRecordCount) {
        this.totalRecordCount = totalRecordCount;
    }

    public void setRecordsPerPage(int recordsPerPage) {
        this.recordsPerPage = recordsPerPage;
    }

    public void setIndexPerPage(int indexPerPage) {
        this.indexPerPage = indexPerPage;
    }

    public void setCurrentPage(int currentPage) {
        this.currentPage = currentPage;
    }

    public void setFirstPage(Page firstPage) {
        this.firstPage = firstPage;
    }

    public void setLastPage(Page lastPage) {
        this.lastPage = lastPage;
    }

    @Override
    public int doStartTag() throws JspException {

        try {
            HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();

            findStartEndIndex();

            this.href = getHref(request);
            for (int i = startIndex; i <= endIndex; i++) {
                pages.add(new Page(i, getURL(i)));
            }

            firstPage = new Page(startIndex, getURL(1));
            lastPage = new Page(lastIndex, getURL(lastIndex));

            if (currentPage - 1 > 0) {
                previousPage = new Page(currentPage - 1, getURL(currentPage - 1));
            } else {
                previousPage = firstPage;
            }

            if (currentPage + 1 < endIndex || currentPage + 1 < lastIndex) {
                nextPage = new Page(currentPage + 1, getURL(currentPage + 1));
            } else {
                nextPage = lastPage;
            }

            pageContext.setAttribute(this.getName(), this);

        } catch (Exception e) {
            logger.error(e.toString());
            throw new JspException(e);
        }
        return EVAL_BODY_INCLUDE;
    }

    @Override
    public int doEndTag() throws JspException {
        pages.clear();
        firstPage = null;
        lastPage = null;
        previousPage = null;
        nextPage = null;
        return SKIP_BODY;
    }

    private void findStartEndIndex() {

        int interval = indexPerPage;
        lastIndex = totalRecordCount / recordsPerPage;
        if (totalRecordCount % recordsPerPage > 0) {
            lastIndex += 1;
        }

        // logger.debug("interval : {}", interval);
        // logger.debug("last index : {}", lastIndex);
        startIndex = 1;
        while (true) {
            endIndex = startIndex + interval - 1;
            if (currentPage >= startIndex && currentPage <= endIndex && endIndex <= lastIndex) {
                break;
            }

            if (endIndex > lastIndex) {
                endIndex = lastIndex;
                break;
            }
            startIndex = endIndex + 1;
        }

        if (endIndex == 0) {
            endIndex = 1;
        }
    }

    private String getURL(int index) {
        // return href + "&page=" + index;
        String concatUrl = (href.indexOf("?") == -1) ? "?" : "&";
        StringBuffer sb = new StringBuffer();
        sb.append(href).append(concatUrl).append("currentPage=").append(index);
        return sb.toString();
    }

    private String getHref(HttpServletRequest request) {

        String href = (String) request.getAttribute("javax.servlet.forward.request_uri");

        StringBuffer sb = new StringBuffer(256);
        sb.append(href);

        int i = 0;
        @SuppressWarnings("unchecked")
        Enumeration<String> e = request.getParameterNames();
        while (e.hasMoreElements()) {
            String param = (String) e.nextElement();
            if (!param.equals("currentPage")) {
                String[] values = request.getParameterValues(param);
                for (String value : values) {
                    if (i++ == 0) {
                        sb.append("?");
                    } else {
                        sb.append("&");
                    }
                    sb.append(param).append("=").append(value);
                }
            }
        }
        // logger.debug("sb.toString(): {}", sb.toString());
        return sb.toString();
    }

    public class Page {
        private int index = 0;
        private String href = null;

        private Page(int index, String href) {
            this.index = index;
            this.href = href;
        }

        public int getIndex() {
            return index;
        }

        public String getHref() {
            return href;
        }
    }
}

scripts/include/paging.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="custom" uri="/tlds/custom-taglib"%>

<c:set var="page" value="1" />
<c:if test="${!empty param.currentPage}"><c:set var="page" value="${param.currentPage}" /></c:if>
<c:set var="indexPerPage" value="10" />
<c:if test="${!empty param.indexPerPage}"><c:set var="indexPerPage" value="${param.indexPerPage}" /></c:if>
<div class="paging">
    <custom:paging name="paging" recordsPerPage="${recordsPerPage}" totalRecordCount="${totalRecordCount}" currentPage="${page}" indexPerPage="${indexPerPage}">
        <%-- First --%>
        <c:if test="${!empty paging.firstPage.href}">
            <span class="btn"><a href="${paging.firstPage.href}" class="first">처음</a></span>
        </c:if>

        <%-- Prev --%>
        <c:if test="${!empty paging.previousPage.href}">
            <span class="btn"><a href="${paging.previousPage.href}" class="prev">이전</a> </span>
        </c:if>

        <%-- Numbering --%>
        <span class="num">
            <c:forEach var="numbering" items="${paging.pages}" varStatus="status">
              <c:choose>
                <c:when test="${numbering.index eq paging.currentPage}">
                    <strong class="on">${numbering.index}</strong>
                </c:when>

                <c:otherwise>
                    <a href="${numbering.href}" title="${numbering.index} 페이지">${numbering.index}</a>
                </c:otherwise>
              </c:choose>
            </c:forEach>
        </span>

        <%-- Next --%>
        <c:if test="${!empty paging.nextPage.href}">
            <span class="btn"><a href="${paging.nextPage.href}" class="next">다음</a></span>
        </c:if>

        <%-- Last --%>
        <c:if test="${!empty paging.lastPage.href}">
            <span class="btn"><a href="${paging.lastPage.href}" class="last">마지막</a></span>
        </c:if>
    </custom:paging>
</div>