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

2015.04.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에서 확인하실 수 있습니다.

  1. Rothko 2016.11.13 14:06 신고  댓글주소  수정/삭제  댓글쓰기

    Kurien님, 이번 코드에서 '.comt'버튼을 누르면 active클래스를 추가하고, '.cancel'버튼 누르면 active클래스를 삭제하는데, 이 active클래스는 왜 추가했다가, 삭제하는 건가요?? css속성에서 사용하는줄 알았는데, css코드에는 사용 안하는것 같고.... active클래스가 무슨 역할을 해주는 건가요?

    • Kurien 2016.11.14 09:42 신고  댓글주소  수정/삭제

      위의 예제에서는 사용을 안했었지만, active가 있음으로써 얻는 이점이 많습니다.

      현재 활성화 된 댓글 창을 찾을 수도 있고, 활성화 되어있는 창의 스타일을 변경할 수도 있습니다.

      일단 위의 예제에서는 딱히 필요 없는 기능이긴 한 것 같네요.

  2. 지나가던고2 2017.01.04 15:29 신고  댓글주소  수정/삭제  댓글쓰기

    delegate를 쓰는이유가 동적으로 추가된 엘리먼트에 이벤트를 심어주기 위함이라면 live도 가능하지 않을까요?

  3. mgt 2017.01.13 15:34 신고  댓글주소  수정/삭제  댓글쓰기

    저기 댓글에서 1Depth든 2Depth든 수정을 없애려고 합니다.

    저기 코드에서 무언가 변화가 필요할까요?

  4. Swipe75 2017.01.13 22:32 신고  댓글주소  수정/삭제  댓글쓰기

    commentSet 의 html은 왜 담는거죠..?

  5. Swipe75 2017.01.25 18:56 신고  댓글주소  수정/삭제  댓글쓰기

    아닙니다! php 게시판강좌 중에서 제일 알아보기 쉽고 이해하기 쉬운 것이였습니다!

  6. 안녕하세요 2017.07.18 11:31 신고  댓글주소  수정/삭제  댓글쓰기

    안녕하세요! 오늘도 역시 질문드립니다 ㅠㅠ
    삭제 버튼을 누르고 취소를 누르면 삭제 버튼을 누를 시 나오는 비밀번호와 확인버튼이 취소를 눌러도 안없어집니다.... 삭제 취소 삭제 취소를 계속누르면 비밀번호 확인란이 계속해서 중첩되어 나옵니다! ㅠㅠ 뭐가 문제일까요?

  7. 고민 2017.07.20 16:55 신고  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 다른블로그를 통해 웹페이지를 만들다가 댓글다는 파트만 이 블로그를 참조하고 있는데요.
    현재는 오직 html php만을 이용해 만들고 있습니다.
    일단 디자인은 아예 신경 안쓰고 만들고 있고, view 제작4 글 까지는 따라왔는데 이 글부터 이해가 잘 가지않아서요 ㅠㅠ 밑에 <script> 부분과 a href #부분은 html과 php 만으로는 구현이 안되는 건가요??

    • Kurien 2017.07.21 10:14 신고  댓글주소  수정/삭제

      댓글에 해당하는 부분은 php로 만들면 일반적인 홈페이지, 블로그 등에 있는 화면처럼 나올 수가 없습니다.

      자바스크립트를 최소한으로 줄이셔야겠다면, 이 블로그의 댓글 쓰기 방식처럼 javascript로는 팝업만 뜰 수 있도록 처리하고, 나머지는 php로 작업하시면 됩니다.

      하지만 해당 프로세스는 제 블로그엔 나와있지 않으니 별도로 찾아보셔야 할 것 같네요.

  8. ㅁㄴㅇ 2017.07.25 00:12 신고  댓글주소  수정/삭제  댓글쓰기

    감사합니다 잘보고있습니다

  9. 손님1 2017.11.07 19:39 신고  댓글주소  수정/삭제  댓글쓰기

    view.php 에서 타이틀
    작성자, 작성일, 조회, 수정,삭제 목록 이렇게는 나오는데 댓글다는 부분에서
    Fatal error: Call to a member function fetch_assoc() on a non-object in /home/ubuntu/workspace/board/comment.php on line 11
    이렇게 뜨고 있어요
    line 11 부분이 while($row = $result->fetch_assoc()) { 이거 입니다.

    • Kurien 2017.11.22 18:26 신고  댓글주소  수정/삭제

      메일로 질문 보내주신 분 같은데,
      답변 메일 보냈습니다.

      메일 보내주신 분이 아니라면 kurien92@gmail.com으로 메일 보내주시기 바랍니다.