PHP & JavaScript 게시판 사이트 제작 2

저번 글에서는 회원가입 및 로그인 기능을 구현하는 코드에 대하여 설명하였다. 이번 글에서는 글 목록 및 글쓰기 기능을 구현하는 코드에 대하여 설명하겠다. 모든 디자인은 부트스트랩을 사용했다.


1. Header 파일 작성

로그인 이후 상단 바에 사용자 정보와 로그아웃 버튼을 출력한다. (로그아웃 코드는 저번 글을 참고) 로그인 정보 없이 접근하면 로그인 폼으로 돌아간다.

<?php
  if(empty($_SESSION["id"]) && empty($_SESSION["name"])){
      header("Location:../");
  }
?>
<header>
<nav class="navbar">
  <div class="container-fluid">
    <div class="navbar-brand">
        <a href="글 목록 폼 경로"><img src="로고 이미지 경로"></a>
    </div>
    <form method="post" action="로그아웃 처리 파일 경로" id="logoutfrm">
        <span class="fs-7 fw-semibold"><?=$_SESSION['id']?> [ <?=$_SESSION['name']?> ]</span>&nbsp;&nbsp;
        <button class="btn btn-danger logoutbtn">로그아웃 <i class="bi bi-box-arrow-right"></i></button>
    </form>
  </div>
</nav>
</header>

2. 글 DB 생성 및 설정

글 작성 시 글 정보가 저장되는 DB를 생성한다. 글 제목은 너무 길지 않게 100자로 제한을 두고 글 내용은 HTML이 들어갈 수 있게 MEDIUMTEXT 형식으로 설정한다.

CREATE TABLE `board` (
	`no` INT(11) NOT NULL AUTO_INCREMENT,
	`board_title` VARCHAR(100) NOT NULL COLLATE 'utf8_general_ci',
	`board_content` MEDIUMTEXT NOT NULL COLLATE 'utf8_general_ci',
	`board_writer` VARCHAR(50) NOT NULL COLLATE 'utf8_general_ci',
	`board_writer_id` VARCHAR(50) NOT NULL COLLATE 'utf8_general_ci',
	`board_cnt` INT(11) NOT NULL DEFAULT '0',
	`board_date` DATETIME NOT NULL,
	`delYn` ENUM('Y','N') NOT NULL DEFAULT 'N' COLLATE 'utf8_general_ci',
	PRIMARY KEY (`no`) USING BTREE
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;

3. 함수 파일 작성

글 목록과 페이징을 출력하는 함수를 작성한다. 글 목록을 불러올 때 글 목록 폼에 표시할 글 개수만큼 삭제되지 않은 글들만 불러온다. 이때 글 제목은 50자 이상 넘어가면 … 으로 표시해서 생략하고 작성일은 오늘 작성된 글이라면 시간을, 그렇지 않을 때는 시간을 제외한 날짜로 표시한다.

글 목록을 가져오는 함수에서 $mode의 인자값 중 view는 글 페이지를 위한 값이니 이번 글에서는 다루지 않는다.

<?php
    function get_board_data($where, $mode){
        global $mysqli;

        if($mode == "list"){
            if(isset($_GET['page'])){
                $page = $_GET['page'];
            }else{
                $page = 1;
            }

            if(isset($_GET['page_limit'])){
                $list_num = $_GET['page_limit']; 
            }else{
                $list_num = 15; 
            }
            $start_num = ($page-1) * $list_num;

            $sql = " select * from board where delYn = 'N' ".$where." order by no desc limit ".$start_num.", ".$list_num;
        }
        $result = $mysqli->query($sql);

        if($result){
            while($row = $result->fetch_array()){      
                $rows[] = $row;
            }
        }
                          
        if(!empty($rows)){
            foreach($rows as &$row){
                if($mode == "list"){
                    if(strlen($row["board_title"])>50){ 
                        $row["board_title"] = str_replace($row["board_title"],mb_substr($row["board_title"],0,50,"utf-8")."...",$row["board_title"]);
                    }

                    $now = date('Y-m-d');
                    if(substr($row['board_date'], 0,10) == $now){
                        $row["board_date"] = substr($row['board_date'], 11,5);
                    }else{
                        $row["board_date"] = substr($row['board_date'], 0,10);
                    }
                }
            }
        }   

	    if($mode == "list"){    
            $data = array();
            $data["rows"] = $rows;
        }else if($mode == "view"){
            $data = $row;
        }
        
        return $data;
    }

    function get_board_paging($where, $search_type, $search){
        global $mysqli;

        if(isset($_GET["page"])){
            $page = $_GET["page"];
        }else{
            $page = 1;
        }

        $sql = "select * from board where delYn = 'N' ".$where;

        $result = $mysqli->query($sql);
        $row_num = $result->num_rows;
        if(isset($_GET["page_limit"])){
            $list_num = $_GET["page_limit"]; 
        }else{
            $list_num = 15; 
        }
        $block_num = 5; // 블록 당 보여줄 페이지 개수
        $block = ceil($page/$block_num); // 현재 페이지 블록
        $block_start = (($block - 1) * $block_num) + 1; // 블록 시작 번호
        $block_end = $block_start + $block_num - 1; // 블록 끝 번호
        
        $total_page = ceil($row_num / $list_num); // 페이징한 페이지 수
        if($block_end > $total_page) $block_end = $total_page; // 빈 페이지가 나오지 않게 총 페이지 수와 마지막 페이지를 같게 맞춰준다
        $total_block = ceil($total_page/$block_num); //블럭 총 개수
            
        $paging_html = "";

        if($block <= 1){ 
            $paging_html .= "<li class='page-item disabled'><a class='page-link'><i class='bi bi-caret-left-fill'></i></a></li>";
        }else{
            $pre = $block_start - 1;
            $paging_html .= "<li><a href='?page=".$pre."&search_type=".$search_type."&search=".$search."&page_limit=".$list_num."'><i class='bi bi-caret-left-fill'></i></a></li>";
        }

        for($i=$block_start; $i<=$block_end; $i++){ 
            if($page == $i){ 
                $paging_html .= "<li class='page-item active'><a class='page-link' href='#'>".$i."</a></li>";
            }else{
                $paging_html .= "<li class='page-item' aria-current='page'><a class='page-link' href='?page=".$i."&search_type=".$search_type."&search=".$search."&page_limit=".$list_num."'>".$i."</a></li>";
            }
        }
            
        if($block >= $total_block){
            $paging_html .= "<li class='page-item disabled'><a class='page-link' href='#'><i class='bi bi-caret-right-fill'></i></a></li>";
        }else{
            $next = $block_end + 1;
            $paging_html .= "<li><a href='?page=".$next."&search_type=".$search_type."&search=".$search."&page_limit=".$list_num."'><i class='bi bi-caret-right-fill'></i></a></li>";
        }

        return $paging_html;
    }
?>

4. 글 목록 폼 작성

폼 작성전에 반드시 최상위 파일에 Header 파일과 함수 파일을 Include한다.

검색 폼은 작성일, 제목, 제목+내용, 작성자로 검색할 수 있게 작성하고 글 목록 출력 시 몇 개를 출력할지 선택할 수 있는 셀렉트 박스도 작성한다. (작성일에 대한 날짜 입력 버튼과 Datepicker 폼은 이 글을 참고해서 추가) 검색이 되었다면 글 목록 및 페이징을 불러오는 함수로 WHERE 절을 전송한다.

폼 안에 글쓰기 및 목록으로 돌아가기 버튼을 추가한다.

글 목록 출력 시 글이 없다면 예외 처리하고 하단에 페이징을 출력한다.

<form id="frm" action="글 목록 폼 경로" method="get">
	<table class="table table-light">
		<tr class="table-active">
            <td>
                <button type="button" class="btn btn-secondary" onclick="get1Day()">1일</button>
                <button type="button" class="btn btn-secondary" onclick="get1Week()">1주</button>
                <button type="button" class="btn btn-secondary" onclick="get1Month()">1개월</button>
                <button type="button" class="btn btn-secondary" onclick="get6Month()">6개월</button>
                <button type="button" class="btn btn-secondary" onclick="get1Year()">1년</button>
            </td>
            <td colspan="3"></td>
	    </tr>
		<tr>
			<td>
			    <label for="cal01" class="datepicker"><input type="text" name="search_sdate" class="form-control" id="cal01"
				<?php
				    if(isset($_GET["search_sdate"])){
						echo "value='".$_GET["search_sdate"]."'";
					}
				?>
				></label> 
				~ 
				<label for="cal02" class="datepicker"><input type="text" name="search_edate" class="form-control" id="cal02"
				<?php
					if(isset($_GET["search_edate"])){
						echo "value='".$_GET["search_edate"]."'";
					}else{
						echo "value='".date("Y-m-d")."'";
					}
				?>
				></label>
			</td>
			<td>
				<div class="form-group row">
					<select name="search_type" class="form-select">
					    <option value="board_title"<?php if($_GET["search_type"] == "board_title") echo "selected"; ?>>제목</option>
					    <option value="board_content"<?php if($_GET["search_type"] == "board_content") echo "selected"; ?>>제목+내용</option>
                        <option value="board_writer"<?php if($_GET["search_type"] == "board_writer") echo "selected"; ?>>작성자</option>
					</select>&nbsp;
					<input type="text" name="search" class="form-control"
					<?php
						if(isset($_GET["search"])){
							echo "value='".$_GET["search"]."'";
						}
					?>
					>&nbsp;
                    <button type="submit" formaction="board_list" class="btn btn-primary form-control">검색</button>
				</div>
			</td>
			<td>
                <button type="button" class="btn btn-outline-primary" onclick="location.href='글쓰기 폼 경로'">글쓰기 <i class="bi bi-pencil-square"></i></button>
				<button type="button" class="btn btn-secondary" onclick="location.href='글 목록 폼 경로'">목록</button>
			</td>
			<td>
			    <select name="page_limit" onchange="changePageLimit()" class="form-select">
					<option value="15" <?php if($_GET['page_limit'] == "15") echo "selected"; ?>>15개</option>
                    <option value="30" <?php if($_GET['page_limit'] == "30") echo "selected"; ?>>30개</option>
					<option value="50" <?php if($_GET['page_limit'] == "50") echo "selected"; ?>>50개</option>
					<option value="100" <?php if($_GET['page_limit'] == "100") echo "selected"; ?>>100개</option>
				</select>
			</td>
		</tr>
	</table>
</form>
<?php
    $where = " ";

    if($_GET["search_type"]){
		if($_GET["search_type"]){
			if($_GET["search_type"] == "board_content"){
				$where .= " and board_title like '%".$_GET["search"]."%' or board_content like '%".$_GET["search"]."%'";
			}else{
				$where .= " and ".$_GET["search_type"]." like '%".$_GET["search"]."%'";
			}
		}

		if($_GET["search_sdate"]){
			$where .= " and DATE_FORMAT(board_date,'%Y-%m-%d') >= '".$_GET["search_sdate"]."'";						
		}

		if($_GET["search_edate"]){
			$where .= " and DATE_FORMAT(board_date,'%Y-%m-%d') <= '".$_GET["search_edate"]."'";						
		}
	}

	$data= get_board_data($where, "list");
	if(!empty($data["rows"])){
		foreach($data["rows"] as $row){
?>
			<div class="list-group w-auto">
				<a href="글 페이지 경로?no=<?=$row["no"]?>" class="list-group-item list-group-item-action py-3">
				<div class="d-flex container text-center">
					<div class="row">
						<span class="fs-5 col-9"><b><?=$row["board_title"]?></b></span>
						<span class="opacity-85 fs-6 col"><?=$row["board_writer"]?></span>
						<span class="opacity-85 fs-6 col"><?=$row["board_date"]?></span>
						<span class="opacity-85 fs-6 col-1"><?=$row["board_cnt"]?></span>
					</div>
				</div>
				</a>
			</div>
<?php
        }
    }else{
?>
		<div class="list-group w-auto">
			<a class="list-group-item list-group-item-action py-3">
				<div class="d-flex justify-content-center">
					<span class="fs-5">검색된 내역이 없습니다.</span>
				</div>
			</a>
		</div>
<?php
	}
?>
<br>
<div>
	<ul class="pagination justify-content-center">
		<?=get_board_paging($where, $_GET["search_type"], $_GET["search"])?>
	</ul>
</div>

5. 글 목록 폼 동작 구현 파일 작성

글 목록 출력 개수를 선택할 수 있는 셀렉트 박스의 값을 선택하면 폼을 바로 전송한다. (작성일에 대한 날짜 입력 버튼과 Datepicker 동작 구현 코드는 이 글을 참고해서 추가)

function changePageLimit(){
	document.getElementById("frm").submit();
}

6. 글쓰기 폼 작성

글쓰기 폼은 제목과 내용을 입력할 수 있게 작성한다. 내용 입력 폼에는 Naver SmartEditor를 사용한다. (Naver SmartEditor 폼은 이 글을 참고해서 추가) 글 제목은 DB구조와 마찬가지로 100자로 제한한다.

<form id="writefrm" action="데이터 처리 파일 경로" method="post" enctype="multipart/form-data">
    <textarea class="form-control mb-1" name="board_title" id="board_title" rows="1" maxlength="100" placeholder="제목을 입력해 주세요."></textarea>
    <textarea class="form-control" name="board_content" id="ir1" rows="20"></textarea>
</form><br>
<div class="pagination justify-content-end">
    <button type="button" class="btn btn-secondary" onclick="location.href='글 목록 폼 경로'">취소</button>&nbsp;
    <button type="button" id="write_btn" class="btn btn-primary">등록</button>
</div>

<script type="text/javascript" src="글쓰기 동작 구현 파일 경로"></script>

7. 글쓰기 동작 구현 파일 작성

글쓰기 버튼을 클릭하면 제목과 내용의 빈값 검사를 하고 폼을 전송한다. (Naver SmartEditor 동작 구현 코드는 이 글을 참고해서 추가)

$("#write_btn").click(function(){
    if($("#board_title").val() == ""){
        alert("제목을 입력해 주세요.");
        return false;
    }
    
    oEditors.getById["ir1"].exec("UPDATE_CONTENTS_FIELD", []);
    var product_detail = $("#ir1").val();

    if(product_detail == "" || product_detail == "<br>" || product_detail == "<p>&nbsp;</p>"){
        alert("내용을 입력해 주세요.");
        return false;
    }

    $("#writefrm").submit();
});

8. 데이터 처리 파일 작성

글쓰기 시 값이 모두 잘 들어왔는지 확인 후 DB에 데이터를 저장하고 글 목록 폼으로 이동한다.

<?php
    global $mysqli;
    if(!empty($_POST["board_title"]) && !empty($_POST["board_content"])){
        $column = "";
        $values = "";

        foreach($_POST as $key => $value){
            $column .= $key.", ";
            $values .= "'".$value."', ";
        }

        $sql = "insert into board (".$column." board_writer, board_writer_id, board_date) values (".$values." '".$_SESSION["name"]."', '".$_SESSION["id"]."', now())";
        $result = $mysqli->query($sql);
        if($result){
            echo "<script>alert('글이 등록되었습니다.');location.replace('글 목록 폼 경로');</script>";
        }else{
            echo "<script>alert('문제가 발생했습니다. 관리자에게 문의해 주세요.');</script>";
        }
    }
?>

글쓰기 후 페이지 이동이 잘 되는지 확인하고 글 목록 폼의 나머지 요소들도 잘 동작하는지 확인한다.


위로 스크롤