16. PHP 게시판 만들기, view 제작 5

2015. 4. 29. 09:24
저자 : Kurien

주의: 본 게시판은 보안을 생각하지 않고 만들어졌으므로 실제로 사용되어서는 안되는 코드입니다.

공부할 때 게시판이 이처럼 동작한다는 정도로만 이해해주세요.


후...

놀다가 이렇게 연재가 늦어진게 아닙니다 ㅠ

여러모로 바쁘기도 하고, 야근도 하다보니 늦어지네요.


20150429_project.zip


게다가 아래의 코드를 보시면 아시겠지만, 상당히 길어졌습니다... ㅋㅋㅋ

오늘 중점적으로 볼 부분은 댓글 쓰기, 2Depth 댓글 쓰기, 수정, 삭제입니다.


2Depth 쓰기만 나눠서 먼저 하려 했는데 하다보니 수정, 삭제까지 동시에 적게되네요.

바로 코드를 보면서 설명드리겠습니다.


<?php

$sql = 'select * from comment_free where co_no=co_order and b_no=' . $bNo;

$result = $db->query($sql);

?>

<div id="commentView">

<form action="comment_update.php" method="post">

<input type="hidden" name="bno" value="<?php echo $bNo?>">

<?php

while($row = $result->fetch_assoc()) {

?>

<ul class="oneDepth">

<li>

<div id="co_<?php echo $row['co_no']?>" class="commentSet">

<div class="commentInfo">

<div class="commentId">작성자: <span class="coId"><?php echo $row['co_id']?></span></div>

<div class="commentBtn">

<a href="#" class="comt write">댓글</a>

<a href="#" class="comt modify">수정</a>

<a href="#" class="comt delete">삭제</a>

</div>

</div>

<div class="commentContent"><?php echo $row['co_content']?></div>

</div>

<?php

$sql2 = 'select * from comment_free where co_no!=co_order and co_order=' . $row['co_no'];

$result2 = $db->query($sql2);

while($row2 = $result2->fetch_assoc()) {

?>

<ul class="twoDepth">

<li>

<div id="co_<?php echo $row2['co_no']?>" class="commentSet">

<div class="commentInfo">

<div class="commentId">작성자:  <span class="coId"><?php echo $row2['co_id']?></span></div>

<div class="commentBtn">

<a href="#" class="comt modify">수정</a>

<a href="#" class="comt delete">삭제</a>

</div>

</div>

<div class="commentContent"><?php echo $row2['co_content'] ?></div>

</div>

</li>

</ul>

<?php

}

?>

</li>

</ul>

<?php } ?>

</form>

</div>

<form action="comment_update.php" method="post">

<input type="hidden" name="bno" value="<?php echo $bNo?>">

<table>

<tbody>

<tr>

<th scope="row"><label for="coId">아이디</label></th>

<td><input type="text" name="coId" id="coId"></td>

</tr>

<tr>

<th scope="row">

<label for="coPassword">비밀번호</label></th>

<td><input type="password" name="coPassword" id="coPassword"></td>

</tr>

<tr>

<th scope="row"><label for="coContent">내용</label></th>

<td><textarea name="coContent" id="coContent"></textarea></td>

</tr>

</tbody>

</table>

<div class="btnSet">

<input type="submit" value="코멘트 작성">

</div>

</form>


<script>

$(document).ready(function () {

var action = '';


$('#commentView').delegate('.comt', 'click', function () {

//현재 위치에서 가장 가까운 commentSet 클래스를 변수에 넣는다.

var thisParent = $(this).parents('.commentSet');


//현재 작성 내용을 변수에 넣고, active 클래스 추가.

var commentSet = thisParent.html();

thisParent.addClass('active');

//취소 버튼

var commentBtn = '<a href="#" class="addComt cancel">취소</a>';

//버튼 삭제 & 추가

$('.comt').hide();

$(this).parents('.commentBtn').append(commentBtn);

//commentInfo의 ID를 가져온다.

var co_no = thisParent.attr('id');

//전체 길이에서 3("co_")를 뺀 나머지가 co_no

co_no = co_no.substr(3, co_no.length);

//변수 초기화

var comment = '';

var coId = '';

var coContent = '';

if($(this).hasClass('write')) {

//댓글 쓰기

action = 'w';

//ID 영역 출력

coId = '<input type="text" name="coId" id="coId">';

} else if($(this).hasClass('modify')) {

//댓글 수정

action = 'u';

coId = thisParent.find('.coId').text();

var coContent = thisParent.find('.commentContent').text();

} else if($(this).hasClass('delete')) {

//댓글 삭제

action = 'd';

}

comment += '<div class="writeComment">';

comment += ' <input type="hidden" name="w" value="' + action + '">';

comment += ' <input type="hidden" name="co_no" value="' + co_no + '">';

comment += ' <table>';

comment += ' <tbody>';

if(action !== 'd') {

comment += ' <tr>';

comment += ' <th scope="row"><label for="coId">아이디</label></th>';

comment += ' <td>' + coId + '</td>';

comment += ' </tr>';

}

comment += ' <tr>';

comment += ' <th scope="row">';

comment += ' <label for="coPassword">비밀번호</label></th>';

comment += ' <td><input type="password" name="coPassword" id="coPassword"></td>';

comment += ' </tr>';

if(action !== 'd') {

comment += ' <tr>';

comment += ' <th scope="row"><label for="coContent">내용</label></th>';

comment += ' <td><textarea name="coContent" id="coContent">' + coContent + '</textarea></td>';

comment += ' </tr>';

}

comment += ' </tbody>';

comment += ' </table>';

comment += ' <div class="btnSet">';

comment += ' <input type="submit" value="확인">';

comment += ' </div>';

comment += '</div>';

thisParent.after(comment);

return false;

});

$('#commentView').delegate(".cancel", "click", function () {

$('.writeComment').remove();

$('.commentSet.active').removeClass('active');

$('.addComt').remove();

$('.comt').show();

return false;

});

});

</script>


보기만 해도 많지 않나요?

설명을 할 자신도 없어지네요.

자바스크립트를 이용했더니 거의 두 페이지 분량의 코드가 한 페이지에 들어간 것 같네요.


바로 코드 분석에 들어가보겠습니다.

<form action="comment_update.php" method="post">

<input type="hidden" name="bno" value="<?php echo $bNo?>">


......

</form>


먼저 댓글이 출력되는 부분에 comment_update.php로 전송하는  form 태그를 만들어줍니다.

이 부분이 있어야 2Depth 댓글이나 수정, 삭제에 필요한 input의 값이 전송됩니다.

그리고 함께 전송시켜준 $bNo(게시물 번호)를 함께 적어줍니다.


<div id="co_<?php echo $row['co_no']?>" class="commentSet">

<div class="commentInfo">

<div class="commentId">작성자: <span class="coId"><?php echo $row['co_id']?></span></div>

<div class="commentBtn">

<a href="#" class="comt write">댓글</a>

<a href="#" class="comt modify">수정</a>

<a href="#" class="comt delete">삭제</a>

</div>

</div>

<div class="commentContent"><?php echo $row['co_content']?></div>

</div>


이 부분은 1Depth의 댓글이 출력되는 부분인데요. 

전에 있던 부분이긴 한데 많이 바뀌었습니다.


div의 id에는 co_<?php echo $row['co_no']?>이 있는데, 이 댓글의 co_no이 몇인지를 알 수 있게 해뒀습니다.

class는 CSS와 자바스크립트에서 사용하기 위해서 지정해놨구요.


아래 있는 commentInfo는 CSS에서 float 속성을 overflow: hidden;으로 막기 위해서 각 div를 감쌌습니다.

CSS에 대한 자세한 부분은 생략하겠습니다.


그 내부에는 commentId와 commentBtn이 있는데, commentId는 댓글의 아이디가 출력되는 부분으로,

coId라는 span 태그가 하나 더 있는데, 이 부분은 자바스크립트에서 순수 아이디 값만을 얻어내기 위해서 감싸둔 부분입니다.

commentBtn은 댓글과 관련된 버튼(추가, 수정, 삭제)를 감싸두었습니다.


그리고 다음으로 댓글의 내용이 출력되는 commentContent가 있겠습니다.


<div id="co_<?php echo $row2['co_no']?>" class="commentSet">

<div class="commentInfo">

<div class="commentId">작성자: <span class="coId"><?php echo $row2['co_id']?></span></div>

<div class="commentBtn">

<a href="#" class="comt modify">수정</a>

<a href="#" class="comt delete">삭제</a>

</div>

</div>

<div class="commentContent"><?php echo $row2['co_content'] ?></div>

</div>


이번엔 2Depth 댓글이 출력되는 부분입니다.

이 부분도 1Depth와 다른 점은 거의 없습니다.

저번에 봤듯이 $row 대신 $row2를 사용한다는 것과 제 댓글에서는 2Depth를 초과하는 Depth는 없기 때문에 댓글 버튼이 없다는 점이죠.


나머지는 1Depth와 같으니 생략하겠습니다.


<script>

$(document).ready(function () {

var action = '';


......

});

</script>


자바스크립트 부분 시작입니다.

이 부분은 짜다보니 무지하게 길어졌네요.


여기서 action 변수는 두 이벤트에서 같이 사용되는 부분이므로 밖에서 선언해줬습니다.

$(document).ready(function () {}); 부분은 jQuery에서 가장 기초가 되는 부분이니 모르시겠다면 jQuery 정도는 배워보시는게 좋습니다.

jQuery를 모르신다면 이 부분은 이해하기가 좀 힘들 수도 있습니다.


$('#commentView').delegate('.comt', 'click', function () {

......

});


$('#commentView').delegate('.comt', 'click', function () { }); 이 부분은 자바스크립트, 정확히는 jQuery의 이벤트입니다.

delegate 이벤트는 잘 안쓰지만, 여기서는 써야할 이유가 있었습니다.


delegate


$('.comt').click(function () { }); 이 이벤트로도 위와 거의 흡사한 효과를 낼 수 있는데요.

하나의 큰 차이점이 있습니다.


만약 아래와 같은 이벤트가 있을 때, 

$('.comt').click(function () { $('body').append('<a href="#" class="comt">임시버튼</a>'); });


.comt를 클릭해서 새로운 임시버튼이 만들어 진다면,

이 임시버튼이 comt라는 클래스를 가지고 있더라도 또 다른 임시버튼을 만들 수 없습니다.

이벤트를 상속받지 못한거죠.


만약 아래처럼 delegate 이벤트가 있을 때는 결과가 달라집니다.

$('#commentView').delegate('.comt', 'click', function () { $('body').append('<a href="#" class="comt">임시버튼</a>'); });


$('#commentView')에 있는 문서객체에 한하여 click 이벤트를 상속받게 되는데요.

여기서 delegate의 첫 번째 매개변수인 '.comt'이 제한을 주는 부분입니다.

$('#commentView')에 있는 문서객체 중에 .comt에 한하여 이벤트를 상속받게 되는거죠.


이걸로 새로 추가되는 comt 클래스를 가진 문서객체도 이벤트를 상속 받을 수 있게 되는겁니다.


//현재 위치에서 가장 가까운 commentSet 클래스를 변수에 넣는다.

var thisParent = $(this).parents('.commentSet');


//현재 작성 내용을 변수에 넣고, active 클래스 추가.

var commentSet = thisParent.html();

thisParent.addClass('active');


//취소 버튼

var commentBtn = '<a href="#" class="addComt cancel">취소</a>';

//버튼 삭제 & 추가

$('.comt').hide();

$(this).parents('.commentBtn').append(commentBtn);


//commentInfo의 ID를 가져온다.

var co_no = thisParent.attr('id');


//전체 길이에서 3("co_")를 뺀 나머지가 co_no

co_no = co_no.substr(3, co_no.length);


delegate 이벤트 내부를 보겠습니다.


thisParent는 현재 위치에서 가장 가까운 commentSet 클래스를 가진 문서객체를 변수에 넣어줍니다.

가장 많이 사용되는 부분이기 때문에 이렇게 변수를 따로 만들었습니다.


commentSet 변수에는 현재 누른 .comt 의 부모 객체 중 commentSet이라는 클래스를 찾아서 내부의 html을 저장합니다.

그리고 commentSet 클래스에 active라는 클래스를 추가로 넣어주었습니다.


commentBtn 변수는 만약 댓글, 수정, 삭제 버튼을 눌렀을 때 나오는 취소 버튼을 넣었습니다.


그 다음은 버튼 삭제 & 추가 부분입니다.

말이 삭제지 그냥 기존의 버튼은 hide()를 통해 숨겨놓고 위에서 만들어놓은 변수 취소(commentBtn)버튼을 출력합니다.

수정을 누르려 했는데 삭제를 잘못 눌렀다면 다시 되돌아 갈 필요가 있으니까요.


co_no은 DB의 co_no(댓글 번호)를 말하는데, 아까 위에서 만들었던 부분 중에 id="co_<?php echo $row['co_no']?>" 라는 부분이 있었죠?

이 attr('id')로 id 값을 가져옵니다.


이 부분의 아이디 값은 항상 co_번호이므로, 받아온 co_no 변수로 co_no.substr(3, co_no.length);를 실행해서 숫자 값만을 받아옵니다.


//변수 초기화

var comment = '';

var coId = '';

var coContent = '';


if($(this).hasClass('write')) {

//댓글 쓰기

action = 'w';

//ID 영역 출력

coId = '<input type="text" name="coId" id="coId">';


} else if($(this).hasClass('modify')) {

//댓글 수정

action = 'u';

coId = thisParent.find('.coId').text();

coContent = thisParent.find('.commentContent').text();

} else if($(this).hasClass('delete')) {

//댓글 삭제

action = 'd';

}


여기서 변수를 초기화하는데, comment는 뒤에서 내용을 더 추가하기 위해서고,

coId는 write와 modify에선 다른 값이 추가되는데 delete 부분은 값이 필요 없기 때문에 공백을 넣어준겁니다.

coContent도 수정 할 때는 값이 추가되는데, 다른 부분일 때는 공백이여야 하므로 초기화 했습니다.


이제 $(this)에 hasClass를 해서 write, modify, delete를 나눕니다.

여기서 $(this)는 .comt(댓글, 수정, 삭제) 버튼을 말합니다.


write일 때는 action을 w를 입력하고 coId에 '<input type="text" name="coId" id="coId">';를 입력합니다.


2Depth의 댓글을 쓸 때는 id, password, content 부분 3개가 필요한데 댓글을 수정, 삭제 할 때는 id 부분의 input 태그는 필요가 없기 때문에

write 일 때만 coId에 input을 넣어줍니다.


만약 modify라면 , action에는 u를 입력하고 coId에 thisParent 변수의 내부의 .coId를 찾아 text()를 받아옵니다.

coContent에는 thisParent 변수 내부에서 .commentContent를 찾아서 text()를 받아옵니다.


댓글 삭제일 때는 action 값만 d를 입력합니다.


comment += '<div class="writeComment">';

comment += ' <input type="hidden" name="w" value="' + action + '">';

comment += ' <input type="hidden" name="co_no" value="' + co_no + '">';

comment += ' <table>';

comment += ' <tbody>';

if(action !== 'd') {

comment += ' <tr>';

comment += ' <th scope="row"><label for="coId">아이디</label></th>';

comment += ' <td>' + coId + '</td>';

comment += ' </tr>';

}

comment += ' <tr>';

comment += ' <th scope="row">';

comment += ' <label for="coPassword">비밀번호</label></th>';

comment += ' <td><input type="password" name="coPassword" id="coPassword"></td>';

comment += ' </tr>';

if(action !== 'd') {

comment += ' <tr>';

comment += ' <th scope="row"><label for="coContent">내용</label></th>';

comment += ' <td><textarea name="coContent" id="coContent">' + coContent + '</textarea></td>';

comment += ' </tr>';

}

comment += ' </tbody>';

comment += ' </table>';

comment += ' <div class="btnSet">';

comment += ' <input type="submit" value="확인">';

comment += ' </div>';

comment += '</div>';

thisParent.after(comment);

return false;


첫 번째 이벤트에서 마지막 부분인 comment 변수 입력 부분입니다.

아까 초기화 했던 comment 변수에 += 연산을 이용해서 해당 값을 입력해줍니다.


공통된 부분은 comment 변수에 그냥 입력하고, input name='w'에는 각기 다른 action 값을 넣어줍니다.

공통된 input name="co_no"에는 id 값에서 추려낸 co_id 변수의 값을 넣었습니다.


action 변수가 d일때는 아이디, 내용 부분은 필요 없으므로 if문을 통해서 d가 아닐 때만 comment 변수에 넣어줍니다.

id 부분에서 coId 변수에는 action이 w일 경우 input 태그가 나오게 되고, u일 경우에는 단순 아이디가 텍스트로 출력됩니다.


이런식으로 comment 변수를 완성하고 thisParent.after(comment)를 통해서 만든 comment 변수를 출력합니다.

마지막에 return false;를 적어주지 않게 되면 a 태그를 누를 때마다 스크롤이 최상단으로 이동하게 됩니다.

꼭 써주세요!



위의 기능을 통해서 만든 부분입니다.

댓글 버튼을 누르면 댓글 아래 추가로 댓글 폼이 생성됩니다.


다음은 두 번째 delegate 부분입니다.


$('#commentView').delegate(".cancel", "click", function () {

......

});


여기서는 .cancel, 취소에 대한 이벤트를 추가해줍니다.


$('.writeComment').remove();

$('.commentSet.active').removeClass('active');

$('.addComt').remove();

$('.comt').show();

return false;


위에서 봤던 첫 번째 delegate 이벤트에서 얻은 action 값을 가지고 연산을 합니다.


먼저 이벤트가 실행되면(취소를 누르면) .wrtieComment를 삭제해줍니다.

여기서 .writeComment 부분은 댓글 작성, 수정을 위해 새로 추가된 폼입니다.

그리고 .commentSet.active(아까 추가한 active 클래스를 가진 .commentSet)에서 active를 삭제하고,

.addComt(추가된 버튼)을 모두 삭제합니다.


그 다음 아까 숨겨놨던(hide()) .comt 클래스를 가진 버튼들을 show()를 통해 다시 보여줍니다.


여기까지 하셨다면 펼치기/숨기기와 같은 기능처럼 됐을겁니다.


원래는 이 글에서 comment_update.php 부분도 다룰 생각이였는데 이 글 쓰는것만 2시간이 넘게 걸리네요;;;

사이사이에 조금 불필요한 부분은 수정/제거 하느라 시간이 조금 더 걸린 것 같습니다.


일단 업로드는 comment_update.php도 수정된 걸로 넣어 두었으니 먼저 보고 계셔도 좋을 것 같네요.

정상 작동이 안된다거나 기술적으로 문제가 있다고 생각하는 부분은 댓글에 남겨주세요^^


자세한 사항은 http://kurien.dothome.co.kr에서 확인하실 수 있습니다.