본문 바로가기
Node.js/Node 프로젝트 예제

[Node.js] 게시판(2/4) 게시판 페이칭 처리

by P_eli 2023. 12. 21.
728x90
반응형

게시글 목록을 가져오는 라우트는 '/articles' 경로에 설정되어 있습니다. 클라이언트는 페이지 번호를 쿼리 매개변수로 전달할 수 있습니다.

 

먼저 server.js 에 가서 게시판 목록 불러오는 부분 코드를 수정합니다.

// server.js

// 게시판에서 게시판 목록을 가져오는 라우트입니다.
app.get('/articles', (req, res) => {
    const page = parseInt(req.query.page) || 1; // 요청된 페이지 번호를 쿼리 매개변수에서 가져옵니다
    const pageSize = 3; // 이 값을 원하는 대로 조절하세요

    // 요청된 페이지 및 페이지 크기를 기반으로 레코드를 건너뛰기 위한 오프셋을 계산합니다.
    const offset = (page - 1) * pageSize;

    // 'board' 테이블에서 페이지별로 정렬된 레코드를 선택하는 데이터베이스 쿼리를 실행합니다.
    const query = 'SELECT * FROM board ORDER BY idx DESC LIMIT ? OFFSET ?';
    connection.query(query, [pageSize, offset], (err, rows) => {
        if (err) {
            console.error('페이징된 게시글 목록을 가져오는 중 오류 발생:', err);
            // 오류가 발생하면 오류 응답을 전송합니다.
            return res.status(500).send('서버 오류');
        }

        // 'board' 테이블의 총 레코드 수를 가져오는 쿼리
        const countQuery = 'SELECT COUNT(*) AS totalCount FROM board';
        connection.query(countQuery, (err, result) => {
            if (err) {
                console.error('총 레코드 수를 가져오는 중 오류 발생:', err);
                return res.status(500).send('서버 오류');
            }

            const totalCount = result[0].totalCount;
            const totalPages = Math.ceil(totalCount / pageSize);

            // 검색된 레코드 및 최대 페이지 수를 응답으로 전송합니다.
            res.send({ rows, totalPages, page_current: page, page_max: totalPages });
        });
    });
});

 

코드 동작 설명

  • 클라이언트는 '/articles' 경로로 GET 요청을 보내며, 페이지 번호를 쿼리 매개변수로 전달합니다.
  • 요청된 페이지 및 페이지 크기를 기반으로 MySQL 쿼리를 통해 해당 페이지의 게시글을 가져옵니다.
  • 동시에 'board' 테이블의 전체 레코드 수를 가져와 전체 페이지 수를 계산합니다.
  • 검색된 게시글 목록과 페이지 정보를 응답으로 클라이언트에게 전송합니다.

 

그다음 페이징 처리 기능을 HTML에 추가하기 위해 index.html에 가서 아래 코드를 추가합니다.

<!-- index.html -->

<body>
    <!-- 페이지의 제목을 설정합니다. -->
    <title>게시글 목록</title>
    <!-- 페이지 상단에 표시되는 제목입니다. -->
    <h1>게시글 목록</h1>
    
    <!-- 네비게이션 링크 -->
    <nav>
        <ul>
            <!-- 게시글 작성 페이지로 이동하는 링크 -->
            <li><a href="create.html">게시글 작성</a></li>
        </ul>
    </nav>

    <!-- 게시글 목록을 표시하는 부분입니다. -->
    <ul id="articles-list"></ul>

    <!-- Pagination controls -->
    <div id="pagination" class="col-8">
        <ul class="pagination pagination-sm justify-content-center align-items-center h-100 mb-0" id="pageList"></ul>
    </div>

 

스크립트 기능도 추가해줍니다.

// 변수 초기화
const articlesList = document.getElementById('articles-list'); // HTML에서 게시글 목록을 표시할 엘리먼트
const pageListElement = document.getElementById('pageList'); // HTML에서 페이지 목록을 표시할 엘리먼트
let currentPage = 1; // 현재 페이지 변수 초기화
let maxPages = 1; // 최대 페이지 수를 저장하는 변수 초기화

// 함수: 게시글 목록을 가져오는 함수
function fetchArticles(page) {
    const pageSize = 5; // 페이지당 표시할 게시글 수 (원하는 값으로 조절)
    const startIndex = (page - 1) * pageSize; // 가져올 게시글의 시작 인덱스

    // 서버로부터 게시글 데이터 가져오기
    fetch(`http://localhost:3000/articles?page=${page}`)
        .then(response => response.json())
        .then(data => {
            const articles = data.rows; // 서버에서 받아온 게시글 데이터
            articlesList.innerHTML = ''; // 이전에 표시된 게시글 초기화

            // 각 게시글을 리스트에 추가
            articles.forEach(article => {
                const li = document.createElement('li');
                li.innerHTML = `
                    <strong>Title:</strong> ${article.title}<br>
                    <strong>Writer:</strong> ${article.writer}<br>
                    <strong>Content:</strong> ${article.content}<br>
                    <strong>Views:</strong> ${article.view_cnt}<br>
                    <strong>Insert Time:</strong> ${article.insert_time}<br>
                    <strong>Update Time:</strong> ${article.update_time ? article.update_time : 'N/A'}<br>
                    <strong>Delete Time:</strong> ${article.delete_time ? article.delete_time : 'N/A'}<br>
                    <a href="article-details.html?id=${article.idx}">상세 정보 보기</a><br>
                    <hr>
                `;
                articlesList.appendChild(li);
            });

            // 전체 페이지 수 업데이트
            maxPages = data.totalPages;

            // 페이지 목록 업데이트
            renderPaginationControls(data.page_current, data.page_max);
        });
}

// 함수: 페이지 변경 이벤트 처리
function changePage(page) {
    // 페이지가 1보다 작아지지 않도록 보장
    currentPage = Math.max(page, 1);
    // 페이지가 최대 페이지 수를 초과하지 않도록 보장
    currentPage = Math.min(currentPage, maxPages);

    // 페이지를 업데이트하고 게시글을 가져옵니다.
    fetchArticles(currentPage);
}

// 함수: 페이지 목록 렌더링
function renderPaginationControls(page_current, page_max) {
    // 페이지 목록 엘리먼트를 찾지 못한 경우 에러를 방지하기 위해 확인
    if (!pageListElement) {
        console.error("Error in renderPaginationControls: Unable to find the 'pageList' element on the page.");
        return;
    }

    // 이전 페이지 목록 초기화
    pageListElement.innerHTML = '';

    const offset = 2;
    const previousBtnEnabled = page_current > 1; // 이전 버튼 활성화 여부
    const nextBtnEnabled = page_current < page_max; // 다음 버튼 활성화 여부

    // "이전" 버튼 추가
    pageListElement.innerHTML += `
        <li class="page-item ${previousBtnEnabled ? '' : 'disabled'}">
            <a class="page-link" href="#" onclick="changePage(${page_current - 1})" ${previousBtnEnabled ? '' : 'tabindex=-1'}>«</a>
        </li>
    `;

    // 페이지 번호 추가
    for (let i = 1; i <= page_max; i++) {
        if (i == 1 || i == page_max || (i >= page_current - offset && i <= page_current + offset)) {
            pageListElement.innerHTML += `
                <li class="page-item ${page_current == i ? 'active' : ''}">
                    <a class="page-link" href="#" onclick="changePage(${i})">${i}</a>
                </li>
            `;
        } else if (i == 2 || i == page_max - 1) {
            pageListElement.innerHTML += `
                <li><a class="page-link">...</a></li>
            `;
        }
    }

    // "다음" 버튼 추가
    pageListElement.innerHTML += `
        <li class="page-item ${nextBtnEnabled ? '' : 'disabled'}">
            <a class="page-link" href="#" onclick="changePage(${page_current + 1})" ${nextBtnEnabled ? '' : 'tabindex=-1'}>»</a>
        </li>
    `;
}

// 초기 페이지 로딩 시 첫 번째 페이지의 게시글 가져오기
fetchArticles(1);

 

코드 동작 설명

1. 코드 초기화

먼저, 필요한 변수들을 초기화합니다.

// 변수 초기화
const articlesList = document.getElementById('articles-list'); // HTML에서 게시글 목록을 표시할 엘리먼트
const pageListElement = document.getElementById('pageList'); // HTML에서 페이지 목록을 표시할 엘리먼트
let currentPage = 1; // 현재 페이지 변수 초기화
let maxPages = 1; // 최대 페이지 수를 저장하는 변수 초기화

 

2. 함수: 게시글 목록 가져오기

게시글을 가져와 HTML 엘리먼트에 동적으로 추가하는 함수입니다.

// 함수: 게시글 목록을 가져오는 함수
function fetchArticles(page) {
    const pageSize = 5; // 페이지당 표시할 게시글 수 (원하는 값으로 조절)
    const startIndex = (page - 1) * pageSize; // 가져올 게시글의 시작 인덱스

    // 서버로부터 게시글 데이터 가져오기
    fetch(`http://localhost:3000/articles?page=${page}`)
        .then(response => response.json())
        .then(data => {
            const articles = data.rows; // 서버에서 받아온 게시글 데이터
            articlesList.innerHTML = ''; // 이전에 표시된 게시글 초기화

            // 각 게시글을 리스트에 추가
            articles.forEach(article => {
                const li = document.createElement('li');
                li.innerHTML = `
                    <!-- 게시글 내용 추가 -->
                    <a href="article-details.html?id=${article.idx}">상세 정보 보기</a><br>
                    <hr>
                `;
                articlesList.appendChild(li);
            });

            // 전체 페이지 수 업데이트
            maxPages = data.totalPages;

            // 페이지 목록 업데이트
            renderPaginationControls(data.page_current, data.page_max);
        });
}

 

3. 함수: 페이지 변경 이벤트 처리

페이지 변경 버튼을 클릭했을 때 호출되는 함수입니다.

// 함수: 페이지 변경 이벤트 처리
function changePage(page) {
    // 페이지가 1보다 작아지지 않도록 보장
    currentPage = Math.max(page, 1);
    // 페이지가 최대 페이지 수를 초과하지 않도록 보장
    currentPage = Math.min(currentPage, maxPages);

    // 페이지를 업데이트하고 게시글을 가져옵니다.
    fetchArticles(currentPage);
}

 

4. 함수: 페이지 목록 렌더링

페이지 목록을 동적으로 생성하여 페이징 컨트롤을 표시하는 함수입니다.

// 함수: 페이지 목록 렌더링
function renderPaginationControls(page_current, page_max) {
    // 페이지 목록 엘리먼트를 찾지 못한 경우 에러를 방지하기 위해 확인
    if (!pageListElement) {
        console.error("Error in renderPaginationControls: Unable to find the 'pageList' element on the page.");
        return;
    }

    // 이전 페이지 목록 초기화
    pageListElement.innerHTML = '';

    // ... (생략)

    // "이전" 버튼 추가
    pageListElement.innerHTML += `
        <!-- 이전 버튼 추가 -->
    `;

    // 페이지 번호 추가
    for (let i = 1; i <= page_max; i++) {
        // ... (생략)
    }

    // "다음" 버튼 추가
    pageListElement.innerHTML += `
        <!-- 다음 버튼 추가 -->
    `;
}

 

5. 초기 페이지 로딩

페이지가 처음 로딩될 때 초기 게시글을 가져오는 함수를 호출합니다.

// 초기 페이지 로딩 시 첫 번째 페이지의 게시글 가져오기
fetchArticles(1);

 

이제 페이징 기능을 보기 좋게 css코드를 추가시켜 줍니다.

/* 페이징 컨트롤의 스타일을 설정합니다. */
        .pagination {
            display: inline-block;
            margin-top: 20px;
        }

        .pagination li {
            display: inline;
            margin-right: 5px;
        }

        .pagination a {
            color: #007bff;
            padding: 5px 10px;
            text-decoration: none;
            border: 1px solid #007bff;
            border-radius: 3px;
        }

        .pagination .active a {
            background-color: #007bff;
            color: #fff;
        }

        .pagination a:hover {
            background-color: #0056b3;
            color: #fff;
        }

        .pagination .disabled a {
            pointer-events: none;
            cursor: not-allowed;
            background-color: #ccc;
            color: #777;
        }

 

실행 화면

 

 

전체코드는 깃허브에 올려두겠습니다.

https://github.com/phs1579/NodeBoard 를 참조해주세요.

728x90
반응형