Almon Dev

게시판 만들기 #3 (게시글 목록) 본문

모의해킹/웹 개발

게시판 만들기 #3 (게시글 목록)

Almon 2024. 11. 12. 12:40

게시판 만들기

 

게시글 목록

 

category.js

const categories = document.querySelectorAll('.category');
categories.forEach((category) => {
  category.addEventListener('click', (e) => {
    const categoryId = e.target.getAttribute('data-category-id');
    const categoryName = e.target.textContent;

    postList(categoryId, categoryName, 1);
  });
});

category 클래스를 가지고 있는 버튼을 누르면 카테고리 아이디와 카테고리 이름을 가지고 postList함수를 실행합니다.

 

post_list.js

function postList(categoryId, categoryName, pageNum) {
  const url = '/forum/db/category.php';
  const limit = 15;
  const offset = (pageNum - 1) * limit;

  fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json; charset=utf-8',
    },
    body: JSON.stringify({
      categoryId: categoryId,
      categoryName: categoryName,
      limit: limit,
      offset: offset,
    }),
  })
    // .then((res) => res.text())
    // .then((res) => {
    //   console.log(res);
    // });
    .then((res) => res.json())
    .then((posts) => {
      const postContainer = document.querySelector('.post-container');
      const windowTitle = document.querySelector('.post-title');
      const postContent = document.querySelector('.post-content');

      const table = document.createElement('table');
      table.classList.add('post-table');

      const caption = document.createElement('caption');
      const capSpan = document.createElement('span');
      capSpan.innerHTML = '게시글 목록';
      capSpan.classList.add('blind');
      caption.appendChild(capSpan);

      const thead = document.createElement('thead');

      const idTh = document.createElement('th');
      idTh.classList.add('thead-id');
      idTh.textContent = '번호';

      const titleTh = document.createElement('th');
      const spanTh = document.createElement('span');
      spanTh.textContent = '제목';
      spanTh.classList.add('thead-title');
      titleTh.appendChild(spanTh);

      const writerTh = document.createElement('th');
      writerTh.classList.add('thead-writer');
      writerTh.textContent = '작성자';

      const createdAtTh = document.createElement('th');
      createdAtTh.classList.add('thead-date');
      createdAtTh.textContent = '작성일';

      const viewsTh = document.createElement('th');
      viewsTh.classList.add('thead-views');
      viewsTh.textContent = '조회수';

      thead.appendChild(idTh);
      thead.appendChild(titleTh);
      thead.appendChild(writerTh);
      thead.appendChild(createdAtTh);
      thead.appendChild(viewsTh);

      const tbody = document.createElement('tbody');
      posts.forEach((post) => {
        const tr = document.createElement('tr');

        const idTd = document.createElement('td');
        idTd.classList.add('td-id');
        idTd.textContent = post.post_id;

        const titleTd = document.createElement('td');
        const titleWrap = document.createElement('div');
        titleWrap.classList.add('td-title');
        titleWrap.textContent = post.title;
        titleWrap.setAttribute('data-page_num', pageNum);
        titleWrap.setAttribute('data-category_id', categoryId);
        titleWrap.setAttribute('data-category_name', categoryName);
        titleTd.appendChild(titleWrap);

        const writerTd = document.createElement('td');
        writerTd.classList.add('td-writer');
        writerTd.textContent = post.user_id;

        const createdAtTd = document.createElement('td');
        createdAtTd.classList.add('td-date');
        createdAtTd.textContent = post.created_at.split(' ')[0];

        const viewsTd = document.createElement('td');
        viewsTd.classList.add('td-views');
        viewsTd.textContent = post.views;

        tr.appendChild(idTd);
        tr.appendChild(titleTd);
        tr.appendChild(writerTd);
        tr.appendChild(createdAtTd);
        tr.appendChild(viewsTd);

        tbody.appendChild(tr);
      });

      table.appendChild(thead);
      table.appendChild(tbody);

      // 섹션 초기화
      closePost();
      windowTitle.textContent = categoryName;

      // 섹션에 추가
      postContent.appendChild(table);

      // 페이징
      paging(categoryId, categoryName, limit);

      // 섹션 보이게
      postContainer.classList.add('on');
      readPost();
    });
}

category.php에 카테고리 정보와 페이지 정보(limit, offset)를 들고 요청을 보냅니다.

응답으로 게시글의 게시글 번호, 제목, 내용, 조회수, 작성일을 받아와 태그를 생성합니다.

 

paging.js

function paging(categoryId, categoryName, limit) {
  url = '/forum/db/paging.php';
  fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json; charset=utf-8',
    },
    body: JSON.stringify({
      category_id: categoryId,
      category_name: categoryName,
      limit: limit,
    }),
  })
    // .then((res) => res.text())
    // .then((res) => console.log(res));
    .then((res) => res.json())
    .then((res) => {
      const postContent = document.querySelector('.post-content');
      const pagingTag = res.pagingTag;

      console.log(pagingTag);

      postContent.insertAdjacentHTML('beforeend', pagingTag);
    });
}

paging.php에 카테고리 정보와 limit를 포함해 요청을 보냅니다.

postContent인 게시글 목록을 표시하고 있는 태그의 아래에 응답받은 태그를 입력합니다.

 

paging.php

<?php
require_once("../../mysql.php");
$result = json_decode(file_get_contents("php://input"), true) ?? null;
$category_id = $result["category_id"] ?? null;
$category_name = $result["category_name"] ?? null;
$limit = $result["limit"] ?? null;

if ($category_id) {
    $sql = "select count(*) from posts where category_id=$category_id";
    $postCount = runSQL($sql)->fetch_row()[0];

    $pageCount = ceil($postCount / $limit);
    $pageCount = $pageCount <= 1 ? 1: $pageCount;
    $pagingNumTag = "";

    for ($i=1; $i <= $pageCount ; $i++) { 
        $pagingNumTag .= "<a onclick='postList($category_id, \"$category_name\", $i)' class='paging-number'>$i</a>\n";
    }
    
    $pagingTag = "
    <div class='paging-container'>
        <div class='paging-prev'>
            <div class='paging-prev-img'>
                ◁
            </div>
            <span class='paging-prev-text'>
                이전
            </span>
        </div>

        $pagingNumTag
        
        <div class='paging-next'>
            <span class='paging-next-text'>
                다음
            </span>
            <div class='paging-next-img'>
                ▷
            </div>
        </div>
    </div>
    ";


    echo json_encode(["postCount" => $postCount, "pagingTag" => $pagingTag, "limit" => $limit]);
}else {
    echo "fail";
}
    $sql = "select count(*) from posts where category_id=$category_id";
    $postCount = runSQL($sql)->fetch_row()[0];

select count를 이용해서 요청받은 카테고리에 속한 게시글의 숫자를 받아옵니다.

 

    $pageCount = ceil($postCount / $limit);
    $pageCount = $pageCount <= 1 ? 1: $pageCount;
    $pagingNumTag = "";

게시글 수 / 한 페이지 숫자로 필요한 페이지 숫자를 구해줍니다. ceil은 소수점 부분이 있으면 올림 해줍니다.

삼항연산자를 이용해서 $pageCount가 1보다 작거나 같으면 1을 크면 $pageCount를 대입합니다.

 

	for ($i=1; $i <= $pageCount ; $i++) { 
        $pagingNumTag .= "<a onclick='postList($category_id, \"$category_name\", $i)' class='paging-number'>$i</a>\n";
    }

for문을 돌며 $pagingNumTag에 페이지 숫자를 표현할 a태그를 만듭니다.

postList(categoey_id, category_name, page_num)를 onclick 속성에 넣어서 클릭 시 페이지를 변환하도록 합니다.

 

    echo json_encode(["postCount" => $postCount, "pagingTag" => $pagingTag, "limit" => $limit]);

만들어진 태그를 json으로 인코딩해서 응답합니다.

 

category.css

.post-table {
  margin-top: 2rem;
  width: 100%;

  font-size: 13px;
  table-layout: fixed;
}

th {
  padding: 2px;
  margin: 1px 0;
  height: 45px;

  color: #4e4e4e;
}

.thead-id {
  width: 72px;
}

.thead-writer {
  width: 118px;
}

.thead-date {
  width: 80px;
}

.thead-views {
  width: 68px;
}

tr {
  height: 36px;
}

td {
  margin-bottom: 1px;
  padding: 4px 7px;
  height: 36px;

  text-align: center;
  color: #666;

  border-bottom: 1px solid rgba(200, 200, 200, 0.3);
}

.td-title {
  padding-left: 30px;

  text-align: left;
  color: #333;
}

.td-title:hover {
  text-decoration: underline;
  cursor: pointer;
}

 

paging.css

.paging-container {
  margin-top: 2rem;

  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;

  color: #333333;
}

.paging-prev,
.paging-next {
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;

  width: 54px;
  height: 24px;
}

.paging-prev:hover,
.paging-next:hover {
  cursor: pointer;
}

.paging-prev:hover .paging-prev-text,
.paging-next:hover .paging-next-text {
  text-decoration: underline;
}

.paging-prev-img,
.paging-next-img {
  margin-bottom: 4px;
  width: 15px;
  height: 20px;
}

.paging-prev-text,
.paging-next-text {
  width: 26px;
  height: 15px;

  font-size: 13px;
}

.paging-number {
  margin: 0px 2px;
  padding-top: 1px;
  width: 24px;
  height: 24px;

  text-align: center;
  line-height: 24px;

  font-size: 13px;
  color: #333333;
}

.paging-number:hover {
  cursor: pointer;
  text-decoration: underline;
}

.article-info {
  height: 13px;

  font-size: 12px;
  color: #979797;
  line-height: 13px;
}

.article-date {
  height: 16px;
  margin-right: 8px;
}

마무리