JavaScript/javascript

Javascript pagination 기능 구현

BBB.OOO 2023. 6. 29. 15:50
반응형

개발환경 : SpringBoot, Thymeleaf, JQuery, javascript, DataTables

 

JQuery 의 Datatables 를 사용하여 화면 구현 중 DataTables 옵션에 있는 페이징 기능을 사용하지 않고

직접 페이징 기능 구현이 필요하여 개발을 진행했다.

 

필요한 pagination 조건

  • 한 그룹의 선택할 수 있는 페이지 수는 10개이다.
  • 한 페이지의 게시물 개수는 select box 를 사용하여 보여준다.(select box option : 10개, 100개, 1000개, 10000개)
  • 페이지 버튼 이외에 이전, 다음, 맨 앞쪽이동, 맨 뒤쪽이동 버튼이 존재한다.

 

Pagination 기능 구현

일단 페이징 기능을 구현하기 위해선 총 4가지의 값이 필요하다.

  1. 현재 페이지의 페이지 그룹
  2. 화면에 보여질 첫번째 페이지
  3. 화면에 보여질 마지막 페이지
  4. 데이터의 총 개수에 따른 총 페이지 수

 

총 페이지 수
  • 총 페이지 수 = (데이터 총 개수 / 한 페이지에 보여질 목록 개수)

총 페이지 수는 데이터의 총 개수를 가지고 한 페이지에 보여질 목록 개수로 나누면 총 페이지 수가 나온다.

예시)

총 데이터 수 = 492

한 페이지에 보여질 목록 개수 = 20

 

// 총 데이터 수
const totalCount = 492;
// 한 페이지에 보여질 목록 개수
const selectCount = 20;

// 총 페이지 수
const totalPage = Math.ceil(totalCount / selectCount);

Math 의 ceil 함수는 소수점 자리를 올림해주는 함수이다.

=> 492 / 20 = 24.6

=> 24.6 를 올림 하게 되면 총 페이지 수는 25페이지가 나온다.

 

현재 페이지 기준 페이지 그룹
  • 현재 페이지 기준 페이지 그룹 = (현재 페이지 / 화면에 보여질 페이지 그룹 수)

현재 페이지 기준 페이지 그룹은 선택된 현재 페이지를 기준으로 보여질 페이지 그룹 수를 나누면 페이지 그룹이 나온다.

예시)

현재 페이지 = 1 (선택된 페이지)

보여질 페이지 그룹 수 = 10 (아래 이미지 기준 1~10까지의 페이지 개수가 존재)

// 현재 페이지
const currentPage = 1;

// 현재 페이지 기준 페이지 그룹
const pageGroup = Math.ceil(currentPage / 10);

=> 1 / 10 = 0.1

=> 0.1 올림하면 페이지그룹은 1이 된다. 1그룹은 1~10 페이지가 존재한다.

예를 들어 선택된 페이지가 12 라면 12 / 10 = 1.2 가 나오고 올림 시 현재 페이지 기준 페이지 그룹은 2이다.

페이지 그룹 2는 11 ~ 20 까지이다.

 

 

화면에 보여질 마지막 페이지
  • 화면에 보여질 마지막 페이지 = 화면에 보여질 페이지 그룹 * 페이지 그룹 수

화면에 보여질 마지막 페이지는 현재 페이지 기준 페이지 그룹 * 화면에 보여질 페이지 그룹 수를 하면 나온다.

현재 페이지 기준 페이지 그룹은 위에서 구했으며

화면에 보여질 페이지 그룹 수는 페이지 그룹마다 총 10개씩이므로 10이다.

예를 들어 페이지 그룹 수가 5 라고 한다면 1 ~ 5개가 된다.

 

//현재 페이지
let currentPage = 1;

// 현재 페이지 기준 페이지 그룹
let pageGroup = Math.ceil(currentPage / 10);

// 화면에 보여질 마지막 페이지
let last = pageGroup * 10;

현재 선택된 페이지가 1 이고 화면에 보여질 페이지 그룹 수(1 ~ 10페이지)가 10이면

현재 페이지 기준 페이지 그룹은 > 1 / 10 = 0.1 올림하면 페이지 그룹 수는 1이다 (1 ~ 10 페이지)

 

화면에 보여질 마지막 페이지는 현재 페이지 기준 페이지 그룹이 1 이고 페이지 그룹 수가 10이므로 아래와 같다.

=> 1 * 10

=> 화면에 보여질 마지막 페이지는 10 페이지이다.

 

만약 현재 페이지가 1이고 화면에 보여질 페이지 그룹 수가 5 (1 ~ 5페이지) 라면

=> 1(현재 페이지 기준 페이지 그룹) * 5

 

화면에 보여질 첫번째 페이지
  • 화면에 보여질 첫번째 페이지 = 화면에 보여질 마지막 페이지 - ( 화면에 보여질 페이지 그룹 수 - 1 )

화면에 보여질 페이지 그룹수 - 1  한 숫자를 화면에 보여질 마지막 페이지 값에 빼면 첫번째 페이지 수이다.

예시)

현재페이지 = 1

화면에 보여질 페이지 그룹 수(1 ~ 10페이지) = 10

화면에 보여질 마지막 페이지 = 10

=> 10 - (10 - 1) = 1 페이지

=> 화면에 보여질 첫번쨰 페이지는 1이다.

 

만약 현재 페이지가 12라고 한다면 페이지 그룹은 2, 화면에 보여질 마지막 페이지는 20 이다.

=> 20 - (10 - 1) = 11

=> 화면에 보여질 첫번째 페이지는 11이다.

//현재 페이지
let currentPage = 1;

// 현재 페이지 기준 페이지 그룹
let pageGroup = Math.ceil(currentPage / 10);

// 화면에 보여질 마지막 페이지
let last = pageGroup * 10;

// 화면에 보여질 첫번째 페이지
let first = last - (pageGroup * 10);

 

이전, 다음, 가장 앞쪽 이동 버튼, 맨 뒤쪽 버튼
  • 이전 버튼(이전 페이지 그룹 이동) : 화면에 보여질 첫번째 페이지 - 1
  • 다음 버튼(다음 페이지 그룹 이동) : 화면에 보여질 마지막 페이지 + 1
  • 가장 앞쪽 이동 버튼 : 1
  • 맨 뒤쪽 이동 버튼 : 총 페이지 수

 

// 다음 버튼
let next = last + 1;
let prev = first - 1;
let allnext = 25(총 페이지 수)
let allprev = 1 (가장 첫번째 선택 페이지)

현재 선택 페이지 = 1

화면에 보여질 첫번째 페이지 = 1

화면에 보여질 마지막 페이지 = 10

현재 페이지 기준 페이지 그룹 = 1 그룹

총 페이지 수 = 25

 

 

 

이제 페이징에서 필요한 4가지의 값을 모두 구했으며, 화면을 그린다.

 

Javascript
function deotisPagination(desc, totalCount, currentPage, selectCount){

    let htmlStr = '';
    const bootstrap_version = '4';

    if(totalCount <= 10) { return; }

    // 전체 개수에서 페이지에서 보여질 개수로 나누어 총 페이지를 계산
    const totalPage = Math.ceil(totalCount / selectCount);
    // 10개씩 보여질 페이지 그룹
    const pageGroup = Math.ceil(currentPage / 10);

    let last = pageGroup * 10;

    if(last > totalPage) last = totalPage;

    let first = last - (10 - 1) <= 0 ? 1 : last - (10 - 1);

    let next = last + 1;
    let prev = first - 1;

    htmlStr += `<nav>`;
        htmlStr += `<ul class="pagination justify-content-center">`;
            htmlStr += prev > 0 ? "<li class=\"page-item\">" : "<li class=\"page-item disabled\">";
                htmlStr += `<a class="page-link" href="#" id="allprev" data-move="allprev" tabindex="-1">&lt;&lt;</a>`;
            htmlStr += "</li>";
            htmlStr += prev > 0 ? "<li class=\"page-item\">" : "<li class=\"page-item disabled\">";
                htmlStr += `<a class="page-link" href="#" id="prev" data-move="prev" tabindex="-1">&lt;</a>`;
            htmlStr += "</li>";

    for (var i = first; i <= last; i++) {
        htmlStr += `<li id='pageLi-${i}' class="page-item">`;
            htmlStr += `<a class="page-link" href='#' id='page-${i}' data-move="no" data-num='${i}'>${i}</a>`;
        htmlStr += "</li>";
    }

        htmlStr += last < totalPage ? "<li class=\"page-item\">" : "<li class=\"page-item disabled\">";
            htmlStr += `<a class="page-link" href="#" id="next" data-move="next" tabindex="-1">&gt;</a>`;
        htmlStr += "</li>";
        htmlStr += last < totalPage ? "<li class=\"page-item\">" : "<li class=\"page-item disabled\">";
            htmlStr += `<a class="page-link" href="#" id="allnext" data-move="allnext" tabindex="-1">&gt;&gt;</a>`;
        htmlStr += "</li>";
    htmlStr += "</ul>";
htmlStr += `</nav>`;

    $("#"+desc.page_id).html(htmlStr);

    if(bootstrap_version == '4'){
        $('#'+desc.page_id+' ul li').removeClass('active');
        $('#'+desc.page_id+' ul li#pageLi-'+currentPage).addClass('active');
    }else if(bootstrap_version == '5'){
        $('#'+desc.page_id+' ul li a').removeClass('active');
        $('#'+desc.page_id+' ul li a#page-'+currentPage).addClass('active');
    }

    let pid = "#"+desc.page_id;

    $(pid+" a").on('click', function (e) {
        const page = $(this).data().num;
        const pageSize = $("#"+desc.select_id).val();

        if($(this).data().move == 'no'){
            paginationClickEvent(desc, page, pageSize, contextPath+'/reporter/callList/ajax/list/data', totalCount);
        }else {
            let moveValue = $(this).data().move;
            switch(moveValue){
                case "allprev":
                if(prev > 0){
                    paginationClickEvent(desc, 1, pageSize, contextPath+'/reporter/callList/ajax/list/data', totalCount);
                }
                break;
                case "prev":
                if(prev > 0){
                    paginationClickEvent(desc, prev, pageSize, contextPath+'/reporter/callList/ajax/list/data', totalCount);
                }
                break;
                case "next":
                if(last < totalPage){
                    paginationClickEvent(desc, next, pageSize, contextPath+'/reporter/callList/ajax/list/data', totalCount);
                }
                break;
                case "allnext":
                    if(last < totalPage){
            paginationClickEvent(desc, totalPage, pageSize, contextPath+'/reporter/callList/ajax/list/data', totalCount);
                        }
                    break;
            }
    }
});

    $("#"+desc.select_id).change(function () {
        const pageSize = $("#"+desc.select_id).val();
        paginationClickEvent(desc, 1, pageSize, contextPath+'/reporter/callList/ajax/list/data', totalCount);
    });

}

const paginationClickEvent = function(desc, page, pageSize, ajaxUrl, totalCount){
    let ajaxStr = '';

    const startDateArr = desc.str_search3.split(':');
    const startH = startDateArr[0];
    const startM = startDateArr[1];
    const startS = startDateArr[2];

    const endDateArr = desc.str_search4.split(':');
    const endH = endDateArr[0];
    const endM = endDateArr[1];
    const endS = endDateArr[2];

    ajaxStr += 'dateStart=' + desc.str_search1
      + '&' + 'dateEnd=' + desc.str_search2
    + '&' + 'startH=' + startH
    + '&' + 'startM=' + startM
    + '&' + 'startS=' + startS
    + '&' + 'endH=' + endH
    + '&' + 'endM=' + endM
    + '&' + 'endS=' + endS;

if(desc.str_search5 != null && desc.str_search5 != ""){
    ajaxStr += '&' + 'ani=' + desc.str_search5
}

if(desc.str_search6 != null && desc.str_search6 != ""){
    ajaxStr += '&' + 'usid=' + desc.str_search6
}

if(desc.list_search1.length > 0){
    desc.list_search1.forEach((data, index) => {
        ajaxStr += '&' + 'holiday=' + data;
    });
}

if(desc.list_search2.length > 0){
    desc.list_search2.forEach((data, index) => {
        ajaxStr += '&' + 'week=' + data;
    });
}

if(desc.list_search3.length > 0){
    desc.list_search3.forEach((data, index) => {
        ajaxStr += '&' + 'dnis=' + data;
    });
}

ajaxStr += '&' + 'page=' + page
+ '&' + 'pageSize=' + pageSize;

$.ajax({
    url : ajaxUrl,
    type: "POST",
    data: ajaxStr,
    dataType: "html",
    cache: false,
    success: function(data) {
        let table = $('#'+desc.table_id).DataTable();
        table.clear();
        table.rows.add(JSON.parse(data)).draw();
        deotisPagination(desc, totalCount, page, pageSize);
    }
});
}

 

HTML
<div th:id="${desc.page_id}"></div>

 

deotisPagination 함수

실제적으로 페이지를 그리는 함수

 

let pid = "#"+desc.page_id;
$(pid+" a").on('click', function (e) {}

desc.page_id 는 id 값 구분을 위해 Controller 에서 랜덤값으로 던졌으며 그냥 id 값이라고 생각하면 된다.

이렇게 한 이유는 프로젝트 화면 자체가 탭 화면으로 여러개를 불러오는 구조라 구분을 위해 랜덤 id 값을 사용했다.

별 신경 안써도 된다.

$(div id 값).on('click') 이벤트를 통해 페이지 번호를 클릭 했을 때 처리하기 위한 함수이다.

 

$("#"+desc.page_id).html(htmlStr);

 htmlStr 변수에 넣은 html 코드 값을 화면에 그리기

 

  if(bootstrap_version == '4'){
        $('#'+desc.page_id+' ul li').removeClass('active');
        $('#'+desc.page_id+' ul li#pageLi-'+currentPage).addClass('active');
    }else if(bootstrap_version == '5'){
        $('#'+desc.page_id+' ul li a').removeClass('active');
        $('#'+desc.page_id+' ul li a#page-'+currentPage).addClass('active');
    }

bootstrap4 와 5 기준으로 active 가 달라 구분한것이다.

별 다를점은 없고 active 가 들어가는 위치만 다르다.

 

// 페이지네이션에서 페이지 버튼을 클릭했을 때 처리
paginationClickEvent(desc, page, pageSize, contextPath+'/reporter/callList/ajax/list/data', totalCount);


// 가장 맨 앞쪽 페이지 이동 버튼

paginationClickEvent(desc, 1, pageSize, contextPath+'/reporter/callList/ajax/list/data', totalCount);

// 맨 뒤쪽 페이지 이동 버튼
paginationClickEvent(desc, totalPage, pageSize, contextPath+'/reporter/callList/ajax/list/data', totalCount);

// 다음 페이지 그룹 이동 버튼
paginationClickEvent(desc, next, pageSize, contextPath+'/reporter/callList/ajax/list/data', totalCount);

// 이전 페이지 그룹 이동 버튼
paginationClickEvent(desc, prev, pageSize, contextPath+'/reporter/callList/ajax/list/data', totalCount);

위 함수 중 중요한 파라미터는 2번째 page(선택된 페이지 버튼), 3번째 pageSize(화면에 보여질 목록 개수),

totalCount(총 데이터 개수) 이다.

 

맨 앞쪽 페이지로 이동하기 위해서는 선택된 페이지 값을 1로 줬다.

맨 뒤쪽 페이지로 이동하기 위해서는 총 페이지 수를 2번째 파라미터로 줬다.

 

paginationClickEvent 함수

페이지 버튼을 클릭 했을 때 이벤트 처리를 위한 함수

 

$.ajax({
    url : ajaxUrl,
    type: "POST",
    data: ajaxStr,
    dataType: "html",
    cache: false,
    success: function(data) {
        let table = $('#'+desc.table_id).DataTable();
        table.clear();
        table.rows.add(JSON.parse(data)).draw();
        deotisPagination(desc, totalCount, page, pageSize);
    }
});

본인은 DataTables 를 사용하여 테이블을 그렸으며, 페이지 버튼을 따로 구성했기 때문에 ajax 를 날렸다.

페이지 버튼을 클릭 했을 때 ajax 요청을 보내 데이터를 불러오고

DataTables 내용을 삭제하는 .clear(), 데이터를 다시 넣기 위한 rows.add(JSON 데이터).draw() 를 통해

다시 그렸다.

이후에 pagination 함수를 호출해 데이터를 넘겨 그린다.

반응형