부트캠프/TIL

코드스테이츠 프론트엔드 부트캠프 Day 18 - Coz' Mini Hackathon (4) 페이지네이션

하이고니 2023. 1. 9. 16:58

밑에 나오는 discussion 목록이 너무 길어서 10개씩 끊는 페이지네이션을 해보려고 한다.

 

 

간단한 페이지네이션 구현하기

Pagination 페이지네이션은 다수의 콘텐츠를 여러 페이지로 나누고, 이전 또는 다음 페이지로 넘어가거나 특정 페이지로 이동할 수 있는 버튼을 제공하는 기술입니다. 페이지네이션은 공통된 주제

nohack.tistory.com

페이지네이션은 생소해서 위 블로그를 참고했다.

 

 

 

HTML에는 맨 밑에

 

<div class="buttons"></div>

 

한 줄만 추가하면 된다.

 

 

 

CSS로 디자인을 한다.

 

/* 페이지네이션 */

.buttons {
    margin-top: 50px;
    position: relative;
    padding: 1rem 0;
    display: inline-flex;
    justify-content: center;
  }
  
  .button {
    padding: 0 1rem;
    font-size: 1.2rem;
    color: #333;
    background: transparent;
    border: 0;
    outline: 0;
    cursor: pointer;
  }
  
  .button.active,
  .button:hover {
    color: #579BB1;
    font-weight: 600;
    text-decoration: underline;
  }
  
  .prev {
    position: absolute;
    left: -2.5rem;
  }
  
  .next {
    position: absolute;
    right: -2.5rem;
  }
  
  ion-icon {
    padding-top: 0.05rem;
  }

 

ion-icon 은 prev, next 아이콘을 웹에서 불러와서 쓰려고 추가한 것인데, HTML에

 

  <script type="module" src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"></script>
  <script nomodule src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"></script>

 

링크를 추가해서 불러왔다.

 

불러온 아이콘 두개

본격적으로 코드를 짜봐야 하는데, 나도 100% 이해하고 따라한 것은 아니다. 내 방식대로 저 블로그의 코드를 해석해서 써보겠다.

 

1.  변수 세팅

 

const contents = document.querySelector("ul.discussions__container");
const buttons = document.querySelector(".buttons");

const numOfContent = agoraStatesDiscussions.length;
const maxContent = 10;
const maxButton = 5;

const maxPage = Math.ceil(agoraStatesDiscussions.length / maxContent);
let page = 1;

contents: 글 목록을 담기 위한 부모 요소

buttons: 페이지 버튼을 담기 위한 버튼들의 부모 요소

numOfContent: 전체 discussion의 개수. 나는 배열 data를 가지고 있으므로, 그 배열의 길이를 numOfContent에 할당했다.

maxContent: 한 페이지에 보여줄 discussion의 개수

maxButton: 한 화면에 보여줄 페이지 버튼의 개수(이건 agoraStatesDiscussions의 element가 많아져서 51개 이상이 될 때 의미가 생긴다. 한 페이지에 10개씩 들어가면 5 페이지에 50개이므로, 51개가 되면 6페이지가 생겨야하고 그러면 기존에 1 2 3 4 5 만 보이던 페이지 버튼을 바꿔줘야 하기 때문이다.)

maxPage: 글을 모두 보여주기 위해 필요한 페이지의 개수

page: 현재 페이지 (시작 페이지 = 1)

 

maxPage 변수에 Math.ceil를 사용한 이유:

전체 discussion 개수를 10으로 나누고 올리면(17.42xxx => 18) 페이지 개수가 나오기 때문

 

2. 버튼 생성 함수 구현

const makeButton = (id) => {
  const button = document.createElement("button");
  button.classList.add("button");
  button.dataset.num = id;
  button.innerText = id;
  button.addEventListener("click", (e) => {
    Array.prototype.forEach.call(buttons.children, (button) => {
      if (button.dataset.num) button.classList.remove("active");
    });
    e.target.classList.add("active");
    renderContent(parseInt(e.target.dataset.num));
  });
  return button;
};

button 이라는 요소를 만들고 "button"이라는 클래스를 추가한다.

 

 

3. 렌더링 함수 구현

 

여기서부터 이해하는 게 어려웠다. 단순히 10개씩 끊어서 페이지에 집어넣는 개념이 아니라,

이 함수를 통과하면서 모든 자식 요소를 지우고 한 페이지에 10개씩 새로 써넣는 개념이다.

 

const renderContent = (page) => {
  while (contents.hasChildNodes()) {
    contents.removeChild(contents.lastChild);
  }
  for (let id = (page - 1) * maxContent + 1; id <= page * maxContent && id <= numOfContent; id++) {
    contents.append(convertToDiscussion(agoraStatesDiscussions[id - 1]))
  }
}

const renderButton = (page) => {
  // 버튼 리스트 초기화
  while (buttons.hasChildNodes()) {
    buttons.removeChild(buttons.lastChild);
  }
  // 화면에 최대 5개의 페이지 버튼 생성
  for (let id = page; id < page + maxButton && id <= maxPage; id++) {
    buttons.appendChild(makeButton(id));
  }
  // 첫 버튼 활성화(class="active")
  buttons.children[0].classList.add("active");
  buttons.prepend(prev);
  buttons.append(next);

  // 이전, 다음 페이지 버튼이 필요한지 체크

  if (page - maxButton < 1) buttons.removeChild(prev);
  if (page + maxButton > maxPage) buttons.removeChild(next);
};

const rndr = (page) => {
  renderContent(page);
  renderButton(page);
};

rndr(page);

 

이해하면서 쳤지만 한꺼번에 보니 뭐라는지 모르겠다. 하나씩 끊어서 천천히 살펴보자.

 

const renderContent = (page) => {
  while (contents.hasChildNodes()) {
    contents.removeChild(contents.lastChild);
  }
  for (let id = (page - 1) * maxContent + 1; id <= page * maxContent && id <= numOfContent; id++) {
    contents.append(convertToDiscussion(agoraStatesDiscussions[id - 1]))
  }
}

 

1. renderContent 라는 함수 표현식을 쓴다. page를 인자로 받는다.

2. 위에서 선언한 contents에 자식노드가 있다면, 마지막 자식부터 지운다. => 그냥 아무것도 없을 때까지 지우겠다는 의미

 

3. for 문을 이해하려면 우선 makeButton 과 renderButton 함수를 다시 들여다봐야 한다. 나는 '도대체 page 값에 변화를 주는 요인이 무엇인가?'에 대해 이틀 밤낮으로 고민했다. 한 번 가보자.

const makeButton = (id) => {
  const button = document.createElement("button");
  button.classList.add("button");
  button.dataset.num = id;
  button.innerText = id;
  button.addEventListener("click", (e) => {
    Array.prototype.forEach.call(buttons.children, (button) => {
      if (button.dataset.num) button.classList.remove("active");
    });
    e.target.classList.add("active");
    renderContent(parseInt(e.target.dataset.num));
  });
  return button;
};


const renderButton = (page) => {
  // 버튼 리스트 초기화
  while (buttons.hasChildNodes()) {
    buttons.removeChild(buttons.lastChild);
  }
  // 화면에 최대 5개의 페이지 버튼 생성
  for (let id = page; id < page + maxButton && id <= maxPage; id++) {
    buttons.appendChild(makeButton(id));
  }
  // 첫 버튼 활성화(class="active")
  buttons.children[0].classList.add("active");
  buttons.prepend(prev);
  buttons.append(next);

  // 이전, 다음 페이지 버튼이 필요한지 체크

  if (page - maxButton < 1) buttons.removeChild(prev);
  if (page + maxButton > maxPage) buttons.removeChild(next);
};

const rndr = (page) => {
  renderContent(page);
  renderButton(page);
};

rndr(page);

 

1. rndr(page) 가 실행되면 page에 1이 할당된 채로 renderButton 함수 실행됨

2. buttons의 모든 자식들 지움(말이 무섭네요)

3. for문을 돌면서 buttons에 makeButton(id)를 append

 

3-1. page가 1이므로 id의 초기값은 1.

3-2. makeButton(1) 실행

3-3. button이라는 요소 만들고 "button"이라는 클래스 붙여줌

3-4. 버튼 안에 1이라는 값을 넣어줌 (화면에 표시되는 것)

3-5. 버튼의 dataset.num 값을 1로 지정

3-5. dataset.num 값이 없는 모든 버튼의 "active" 클래스를 지움

3-6. 타겟(눌린 버튼)에는 "active" 클래스 넣어줌

3-7. renderContent(1) 실행

3-8. 버튼 "1" 이 buttons에 append 됨

 

4. 이 방식으로 버튼이 총 5까지 append 된다. (maxButton이 5 이므로)

5. 그리고 append 된 버튼은 클릭됐을 때 자기 dataset.num을 renderContent에 입력하면서 함수를 실행시킨다.

예를 들어 3번 버튼을 누르면 id의 초기값은 21이고 30까지 증가할 수 있으므로 agoraStateDiscussions의 20번째 인덱스부터 29번째 인덱스까지를 화면에 표시할 수 있는 것이다!!!!!!!!!!!!!

 

 

버튼의 위치 고정을 위해서 discussion__wrapper 의 높이를 고정시키고 스크롤 기능을 추가했다.

아래는 동작 화면이다.