코드스테이츠 프론트엔드 부트캠프 Day 18 - Coz' Mini Hackathon (4) 페이지네이션
밑에 나오는 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 의 높이를 고정시키고 스크롤 기능을 추가했다.
아래는 동작 화면이다.