게시판 글목록&PageNation (fetch, 이벤트버블링)- Vanilla JavaScript

solda-blue·2023년 2월 28일
0

Java Script

목록 보기
3/3

intro

웹 개발 기본기 다지는데는 게시판 만한게 없는거 같다.
이전에는 학원에서 Vue.js로 게시판을 만들었었는데 그러다 보니까 순수 자바스크립트 실력은 많이 늘지도 않는것 같고 제대로 이해도 하지 못한채로 코드만 치는 것 같아서 이번에는 바닐라 자바스크립트로 게시판 목록과 페이지네이션을 구현해보았다.

밴앤드 구성

어차피 프론트엔드 과정인지라 DB와 서버는 최대한 간단하게 MongoDB와 Express로 간단하게 구성했다.
백엔드 개발에 관해선 다음에 자세하게 다루기로하고 여기선 프론트에서 필요한 만큼만 코드를 살펴보자.

최소한의 검색기능과 5개씩 페이지네이션을 만들어 보았다.

Get 요청 하기

일전에 XMLHttpRequest로 get요청을 보내봤었는데 이번에는 fetch함수를 사용해봤다.

기본 fetch함수 예제와는 모양이 다른데 저런식으로 return 한 항목을 다시 비동기로 변수에 담아주면 응답 받은 내용을 전역변수에 넣어서 사용할 수 있다고 한다.

handleData 함수를 실행하고 list를 찍어보면 제대로 작동하는걸 볼 수 있다.

-Promise에 관해서는 몇개 찾아봤지만 아직 제대로 이해가 되지 않아서 좀 더 공부한 후에 따로 포스트하던지 해야겠다.

데이터 테이블에 바인딩하기

위의 getList()로 가져온 데이터를 화면상에 뿌려주는데 정석(?)으로는 각각 element를 생성하고 안에 데이터를 집어넣는게 맞는것 같지만 그렇게하면 너무 코드가 길어질 것 같으니 tbody안에 반복문을 통하여 통째로 바인딩했다.

문제 없이 잘 나온다.

        <table>
            <thead>
                <tr>
                    <td>번호</td>
                    <td>제목</td>
                    <td>작성자</td>
                    <td>조회수</td>
                    <td>등록일</td>
                </tr>
            </thead>
            <tbody id="tbody">
            </tbody>
        </table>

참고용 table 전체 html

페이지네이션 만들기

페이지네이션은 이번에 공부하면서 새로 배운 DOM과 Nodes 조작을 통해서 전체 게시글의 숫자를 이용해서 필요한 만큼 element를 생성하고 필요한 attribute를 집어넣은 다음에 "pagenation" div안에 집어넣어보았다.

<div id="pagenation">
	createElement로 생성된 버튼들이 들어오는 자리
</div>
const pagenation = document.getElementById('pagenation');

여기서 data-id 를 이용하여 원하는 태그에 속성을 추가해주는걸 볼 수 있는데

data-데이터이름 = 값

을 입력해 주면 해당태그안에 원하는 데이터를 집어넣을 수 있고

해당태그 선택(ex.document.querySelector('...').dataset.데이터이름;

dataset을 통하여 그 값을 불러올수 있게 된다.
간단한 기능이지만 여기저기 유용하게 써먹을 곳이 많은 것 같다.

페이지네이션 버튼 click이벤트 구현

위에서 생성된 버튼들에 click 이벤트를 추가하여 해당 페이지의 게시글 목록이 보여지도록 만들어보자

전체 코드인데 살펴보면 각각의 버튼들에 이벤트리스너를 생성하는 것이 아니라 상위의 pagenation div에 이벤트를 생성해 준것을 볼 수 있다.

이렇게 했을 때 몇가지 이점이 있는데
일단 이벤트가 많아질수록 성능저하가 일어나는데 저런식으로 상위 요소에서 하위요소를 제어하게 되면 이벤트리스너의 개수를 줄여서 성능도 향상되고

전체적인 코드도 줄어들게 되고 지금 내가 할 수 있는 선에선 최대한 함수들로 기능들을 나누어 놓았는데 버튼이 추가된다던지 이런저런 변화에 대해서 확장성도 좋아진다고 볼 수 있다.

이런식으로 코드를 짤 때 한가지 주의할 점이 있는데

지금 버튼들을 감싸고 있는데 pagenation div를 살펴보면 저런식으로 버튼들 사이의 빈 공간들도 클릭했을 때 이벤트가 실행되게 된다.

이런 것을 이벤트 버블링(Event Bubbling)이라고 하는데 어떤 요소의 이벤트가 발생했을 때 상위요소의 이벤트도 같이 전파되는 것을 말한다.

이게 유용할 때도 많은데 반대로 예기치 못한 방법으로 코드가 실행 될 때도 있다.

이런 것을 방지하기 위해서 자바스크립트에 몇가지 기능이 있는데

pagenation.addEventListener('click', function(e) {

}

위의 이벤트 리스너 함수를 살펴보자.
콜백함수 부분에 e라는 파라미터가 들어가 있는것을 볼 수가 있는데 이것을 이벤트 객체(event object)라고 부른다.
이벤트 객체에는 해당 이벤트에 대해 여러가지 정보를 담고 있는데

(console.log(e)로 이벤트리스너를 호출했을 때)

이때 target 이라는 항목이 있다.
target이란 해당이벤트가 발생한 html 요소 "그 자체" 를 의미한다.
그렇기에 위의 이미지를 보면 분명 이벤트리스너가 달린 곳은 pagenation div인데 target에는 내가 클릭한 button 이라는 요소가 출력되는 걸 볼 수 있다.

그래서 내가 상호작용한 요소 그 자체를 선택하고 싶으면 함수 내에서

e.target

으로 불러올 수 있다.
(앞의 e. 으로 붙이는건 함수에 파라미터로 e 를 넣었기 때문, event를 넣었다면 event.target)

그리고 해당 이벤트가 부착(?)된 요소를 지정하고 싶으면

e.currentTarget

을 사용하면 된다.

지금 만든 이벤트에서는 addEventListener가 달린 pagenation div 가 될 것이다.

ex) 게시판 페이지네이션 2번 버튼을 눌렀을 때
e.target에는 내가 실제로 누른 버튼이 출력되고, e.currentTarget에는 이벤트가 만들어진 pagenation div가 나오는 걸 볼 수 있다.
(거기다 아까 전에 data- 방식으로 dataset에 집어넣은 값도 잘 보인다.)

그럼 위의 두가지를 포함하여 이벤트객체를 활용하여 쓸 수 있는 유용한 기능 몇가지를 살펴보면

e.target; // 유저가 실제로 누른거
e.currentTarget; // 이벤트리스너 달린 곳
e.preventDefault(); // 이벤트 기본동작 막아줌
e.stopPropagation(); // 내 상위요소로 이벤트 버블링 막아줌

지금 상황에서는 이정도만 알고 있어도 유용하게 쓸 수 있을 것이다.
-e.preventDefault()는 해당 요소가 기본적으로 제공하는 기능, (예를들면 a태그는 해당 href 주소로 이동한다던가 하는) 태그 원래의 기능들이 실행되는 것을 막아준다.

작성한 코드에서도 if 문을 통하여 e.target 과 e.currentTarget 이 다를 때만 코드가 실행되게 하여서 원치않는 작동을 막아주었다.

이렇게 해서 허접하지만 바닐라 자바스크립트로 게시판 글목록과 페이지네이션 기능을 구현해 보았다.

전체 코드

<body>
    <a href="../index.html"><button>뒤로</button></a>
    <a href="./boardInsert.html"><button>글쓰기</button></a>

    <hr />

    <div id="boardContent">
        <table>
            <thead>
                <tr>
                    <td>번호</td>
                    <td>제목</td>
                    <td>작성자</td>
                    <td>조회수</td>
                    <td>등록일</td>
                </tr>
            </thead>
            <tbody id="tbody">
            </tbody>
        </table>
        
        <hr />
        
        <div id="pagenation"></div>
    </div>

    <script>
        const tbody = document.getElementById('tbody');
        const pagenation = document.getElementById('pagenation');
        const boardContent = document.getElementById('boardContent');

        handleData();
        for(let i = 0; i < tbody.childElementCount; i++) {
            tbody.children[i].setAttribute("class", "board-one");
        }

        async function handleData(page) {
            // fetch로 요청한 response 를 전역변수에 저장 하는 법
            const list = await getData(page);
            boardList(list); // 게시글 목록
            pageList(list); // 페이지네이션
            console.log("최신 페이지 => ", list);
        }

        // getData로부터 게시글 목록 생성
        function boardList(data) {
            for(let tmp of data.result) {
                tbody.innerHTML += 
                `<tr>` +
                    `<td>${tmp._id}</td>` +
                    `<td>${tmp.title}</td>` +
                    `<td>${tmp.writer}</td>` +
                    `<td>${tmp.hit}</td>` +
                    `<td>${tmp.regdate}</td>` +
                `</td>`;
            }
        }

        // getData로부터 페이지네이션 생성
        function pageList(data) {
            let page;
            // data.total에서 page개수 정하기
            if(data.total % 5 == 0) {
                // 5의 배수로 떨어지면 맨 뒤의 떨이페이지 필요x
                page = Math.floor((data.total / 5));
            } else {
                page = Math.floor((data.total / 5) +1);
            }
            // 페이지 버튼이 1부터 시작할 수 있도록 i와 page에 1을 더해준다
            for(let i = 1; i < page + 1; i++) {
                // 페이지 개수만큼 버튼 생성
                let newButton = document.createElement('button');
                newButton.innerHTML = i;
                // 각 버튼 element에 dataset.id 넣기
                newButton.setAttribute("data-id", i);
                newButton.setAttribute("class", "btn");
                // pagenation div에 생성한 버튼 추가
                pagenation.appendChild(newButton);
            }
        }

        // 이벤트 버블링 활용해서 버튼 상위 div에 클릭이벤트 하나만 만들기 => 성능에 좋다
        pagenation.addEventListener('click', async function(e) {
            // 실제 클릭한 요소(e.target)와 실제 이벤트가 만들어진 요소(e.currentTarget)가
            // 다를때만 동작하게 하여 이벤트 버블링을 막음
            if(e.target != e.currentTarget) {
                // e.target으로 클릭한 버튼 찾아내서 서버에서 정보가져오기
                let list = await getData(e.target.dataset.id);
                console.log(e.target);
                tbody.innerHTML = "";
                boardList(list);
                console.log("페이지 번호 => ", e.target.dataset.id, list);
            }
        })

        // fetch로 get요청
        function getData(page) {
            // 응답받은 데이터를 전역변수에 반환
            return new Promise((resolve, reject) => {
                fetch(`http://127.0.0.1:8088/board/select.json?page=${page}`)
                .then((response) => response.json())
                .then(data => {
                    resolve(data);
                });
            });
        }
        
    </script>

outro

이 다음엔 검색기능과 특정 게시글을 열람할 수 있는 기능도 구현해 보자

0개의 댓글