글 관리 기능¶
게시글 관리와 관련된 고급 기능들을 구현하는 방법을 학습합니다.
페이지 이동 없는 글 삭제¶
AJAX를 이용한 글 삭제¶
<!-- 글 삭제 버튼 -->
<div class="document-actions" cond="$oDocument->isGranted()">
<button type="button" onclick="deleteDocumentAjax({$oDocument->document_srl})" class="btn-delete">
삭제
</button>
</div>
<script>
function deleteDocumentAjax(document_srl) {
if(!confirm('정말로 이 글을 삭제하시겠습니까?')) return false;
var params = {
document_srl: document_srl,
module: 'board',
act: 'procBoardDeleteDocument'
};
exec_xml('board', 'procBoardDeleteDocument', params, function(ret_obj) {
if(ret_obj.error != 0) {
alert(ret_obj.message);
} else {
alert('글이 삭제되었습니다.');
// 목록으로 이동
location.href = current_url.setQuery('document_srl', '');
}
});
}
</script>
목록에서 다중 삭제¶
<!-- 관리자용 일괄 삭제 -->
<form id="board-list-form" action="./" method="post">
<input type="hidden" name="module" value="board" />
<input type="hidden" name="act" value="procBoardDeleteDocuments" />
<table class="board-list">
<thead>
<tr>
<th cond="$grant->manager">
<input type="checkbox" onclick="checkAll(this)" />
</th>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>날짜</th>
</tr>
</thead>
<tbody>
<tr loop="$document_list=>$no,$document">
<td cond="$grant->manager">
<input type="checkbox" name="cart[]" value="{$document->document_srl}" />
</td>
<td>{$no}</td>
<td>{$document->getTitle()}</td>
<td>{$document->getNickName()}</td>
<td>{$document->getRegdate('Y.m.d')}</td>
</tr>
</tbody>
</table>
<div class="admin-actions" cond="$grant->manager">
<button type="button" onclick="doDeleteDocuments()" class="btn-delete">
선택 삭제
</button>
<button type="button" onclick="doMoveDocuments()" class="btn-move">
선택 이동
</button>
</div>
</form>
<script>
function doDeleteDocuments() {
var checked = jQuery('input[name="cart[]"]:checked');
if(checked.length == 0) {
alert('삭제할 글을 선택해주세요.');
return false;
}
if(!confirm(checked.length + '개의 글을 삭제하시겠습니까?')) return false;
var params = {
cart: [],
module: 'board',
act: 'procBoardDeleteDocuments'
};
checked.each(function() {
params.cart.push(jQuery(this).val());
});
exec_xml('board', 'procBoardDeleteDocuments', params, function(ret_obj) {
if(ret_obj.error != 0) {
alert(ret_obj.message);
} else {
alert('선택한 글이 삭제되었습니다.');
location.reload();
}
});
}
</script>
비밀글 즉시 변경¶
글보기에서 비밀글 전환¶
<!-- 비밀글 전환 버튼 -->
<div class="document-options" cond="$oDocument->isGranted()">
{@$is_secret = $oDocument->get('is_secret') == 'Y'}
<button type="button" onclick="toggleSecret({$oDocument->document_srl}, '{$is_secret ? 'N' : 'Y'}')" class="btn-secret">
<i class="xi-{$is_secret ? 'unlock' : 'lock'}"></i>
{$is_secret ? '공개글로 변경' : '비밀글로 변경'}
</button>
</div>
<script>
function toggleSecret(document_srl, is_secret) {
var message = is_secret == 'Y' ? '비밀글로 변경하시겠습니까?' : '공개글로 변경하시겠습니까?';
if(!confirm(message)) return false;
var params = {
document_srl: document_srl,
is_secret: is_secret
};
// 커스텀 API 호출
exec_xml('board', 'procBoardUpdateDocumentSecret', params, function(ret_obj) {
if(ret_obj.error != 0) {
alert(ret_obj.message);
} else {
alert('변경되었습니다.');
location.reload();
}
});
}
</script>
추천/비추천 경고창 없이 동작¶
즉시 추천 처리¶
<!-- 추천 버튼 -->
<div class="vote-buttons">
<button type="button" onclick="voteDocument({$oDocument->document_srl}, 1)" class="btn-vote-up" disabled="disabled"|cond="$oDocument->isVoted()">
<i class="xi-thumbs-up"></i>
<span class="vote-count">{$oDocument->get('voted_count')}</span>
</button>
<button type="button" onclick="voteDocument({$oDocument->document_srl}, -1)" class="btn-vote-down" disabled="disabled"|cond="$oDocument->isBlamed()">
<i class="xi-thumbs-down"></i>
<span class="blame-count">{$oDocument->get('blamed_count')}</span>
</button>
</div>
<script>
// 경고창 없이 추천/비추천
function voteDocument(document_srl, point) {
var act = point > 0 ? 'procBoardVoteUp' : 'procBoardVoteDown';
var params = {
target_srl: document_srl,
point: point
};
// 버튼 비활성화
jQuery('.btn-vote-up, .btn-vote-down').prop('disabled', true);
exec_xml('board', act, params, function(ret_obj) {
if(ret_obj.error != 0) {
// 에러 메시지만 표시
showNotification(ret_obj.message, 'error');
jQuery('.btn-vote-up, .btn-vote-down').prop('disabled', false);
} else {
// 성공 시 카운트 업데이트
if(point > 0) {
var count = parseInt(jQuery('.vote-count').text()) + 1;
jQuery('.vote-count').text(count);
jQuery('.btn-vote-up').addClass('voted');
} else {
var count = parseInt(jQuery('.blame-count').text()) + 1;
jQuery('.blame-count').text(count);
jQuery('.btn-vote-down').addClass('voted');
}
showNotification('추천했습니다.', 'success');
}
});
}
// 알림 표시 함수
function showNotification(message, type) {
var notification = jQuery('<div class="notification ' + type + '">' + message + '</div>');
jQuery('body').append(notification);
notification.fadeIn(300).delay(2000).fadeOut(300, function() {
jQuery(this).remove();
});
}
</script>
<style>
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 20px;
background: #333;
color: white;
border-radius: 4px;
display: none;
z-index: 9999;
}
.notification.success {
background: #4CAF50;
}
.notification.error {
background: #f44336;
}
.btn-vote-up.voted,
.btn-vote-down.voted {
color: #2196F3;
}
</style>
댓글 허용 여부 설정¶
글 작성시 댓글 허용 옵션¶
<!-- 글쓰기 폼에 댓글 허용 옵션 추가 -->
<div class="write-option">
<label>
<input type="checkbox" name="allow_comment" value="Y" checked="checked"|cond="$oDocument->allowComment()" />
댓글 허용
</label>
<label>
<input type="checkbox" name="allow_trackback" value="Y" checked="checked"|cond="$oDocument->allowTrackback()" />
트랙백 허용
</label>
<label>
<input type="checkbox" name="notify_message" value="Y" />
댓글 알림 받기
</label>
</div>
<!-- 글 보기에서 댓글 허용 상태 변경 -->
<div class="comment-control" cond="$oDocument->isGranted()">
<button type="button" onclick="toggleCommentAllow({$oDocument->document_srl})" class="btn-comment-toggle">
{$oDocument->allowComment() ? '댓글 차단' : '댓글 허용'}
</button>
</div>
<script>
function toggleCommentAllow(document_srl) {
var params = {
document_srl: document_srl,
allow_comment: window.allow_comment ? 'N' : 'Y'
};
exec_xml('board', 'procBoardUpdateCommentAllow', params, function(ret_obj) {
if(ret_obj.error != 0) {
alert(ret_obj.message);
} else {
location.reload();
}
});
}
</script>
글 관리 도구¶
관리자용 일괄 처리¶
<!-- 관리 도구 모음 -->
<div class="admin-toolbar" cond="$grant->manager">
<h3>글 관리 도구</h3>
<!-- 카테고리 일괄 변경 -->
<div class="tool-item">
<select name="target_category">
<option value="">카테고리 선택</option>
<option loop="$category_list=>$val" value="{$val->category_srl}">
{$val->title}
</option>
</select>
<button type="button" onclick="changeCategoryBulk()">
선택 글 카테고리 변경
</button>
</div>
<!-- 상태 일괄 변경 -->
<div class="tool-item">
<select name="target_status">
<option value="PUBLIC">공개</option>
<option value="SECRET">비밀</option>
<option value="TEMP">임시저장</option>
</select>
<button type="button" onclick="changeStatusBulk()">
선택 글 상태 변경
</button>
</div>
<!-- 복사/이동 -->
<div class="tool-item">
<select name="target_module_srl">
<option value="">게시판 선택</option>
{@
$board_list = getModel('board')->getBoardList();
}
<option loop="$board_list=>$board" value="{$board->module_srl}">
{$board->browser_title}
</option>
</select>
<button type="button" onclick="copyDocumentsBulk()">복사</button>
<button type="button" onclick="moveDocumentsBulk()">이동</button>
</div>
</div>
<script>
// 카테고리 일괄 변경
function changeCategoryBulk() {
var checked = jQuery('input[name="cart[]"]:checked');
var category_srl = jQuery('select[name="target_category"]').val();
if(checked.length == 0) {
alert('글을 선택해주세요.');
return false;
}
if(!category_srl) {
alert('카테고리를 선택해주세요.');
return false;
}
var document_srls = [];
checked.each(function() {
document_srls.push(jQuery(this).val());
});
var params = {
document_srls: document_srls.join(','),
category_srl: category_srl
};
exec_xml('board', 'procBoardAdminUpdateCategory', params, function(ret_obj) {
if(ret_obj.error != 0) {
alert(ret_obj.message);
} else {
alert('카테고리가 변경되었습니다.');
location.reload();
}
});
}
// 게시판 간 이동
function moveDocumentsBulk() {
var checked = jQuery('input[name="cart[]"]:checked');
var target_module_srl = jQuery('select[name="target_module_srl"]').val();
if(checked.length == 0) {
alert('글을 선택해주세요.');
return false;
}
if(!target_module_srl) {
alert('이동할 게시판을 선택해주세요.');
return false;
}
if(!confirm('선택한 글을 이동하시겠습니까?')) return false;
var document_srls = [];
checked.each(function() {
document_srls.push(jQuery(this).val());
});
var params = {
document_srls: document_srls.join(','),
module_srl: target_module_srl,
target_module_srl: target_module_srl
};
exec_xml('board', 'procBoardAdminMoveDocument', params, function(ret_obj) {
if(ret_obj.error != 0) {
alert(ret_obj.message);
} else {
alert('글이 이동되었습니다.');
location.reload();
}
});
}
</script>
확장 변수 활용¶
확장 변수 출력 및 관리¶
<!-- 확장 변수 출력 -->
<div class="extra-vars">
{@$extra_vars = $oDocument->getExtraVars()}
<dl loop="$extra_vars=>$key,$val">
<dt>{$val->name}</dt>
<dd>
<!-- 텍스트 -->
<span cond="$val->type == 'text'">{$val->value}</span>
<!-- 이미지 -->
<img cond="$val->type == 'image' && $val->value" src="{$val->value}" alt="{$val->name}" />
<!-- 날짜 -->
<span cond="$val->type == 'date'">{zdate($val->value, 'Y-m-d')}</span>
<!-- 주소 -->
<div cond="$val->type == 'kr_zip' && $val->value">
{@$addr = unserialize($val->value)}
{$addr[0]} {$addr[1]} {$addr[2]}
</div>
</dd>
</dl>
</div>
<!-- 확장 변수로 필터링 -->
{@
// 특정 확장 변수 값으로 필터링
$filtered_documents = array();
foreach($document_list as $document) {
$extra_vars = $document->getExtraVars();
if($extra_vars->product_type->value == 'premium') {
$filtered_documents[] = $document;
}
}
}
모범 사례¶
- 권한 체크: 모든 관리 기능에 권한 확인 필수
- 확인 절차: 중요한 작업은 confirm 필수
- 로그 기록: 관리 작업은 로그 남기기
- 일괄 처리: 대량 작업시 배치 처리 고려
- UI/UX: 직관적인 인터페이스 제공
다음 단계¶
글 관리 기능을 구현했다면, 갤러리형 게시판을 학습하세요.