<페이지네이션>
- 프론트엔드
// ariticlesList.js
// 페이지네이션
async function paginationView(articles) {
// html에 게시글 목록과 버튼을 js로 만들어서 붙여주기 위해 가져옴
const contents = document.getElementById("article-list-kmj");
const buttons = document.getElementById("buttons-kmj");
const numOfContent = articles.length; // 전체 게시글 수
const maxContent = 10; // 한 페이지당 게시글 수
const maxButton = 5; // 한 창에 보여질 버튼 수
const maxPage = Math.ceil(numOfContent / maxContent); // 최종 페이지 수
let page = 1; // 페이지 초기화
const makeContent = (id) => {
// 이전에 게시글 목록 페이지 만들었을 때 그 HTML을 가져와서 페이지네이션으로 새로 만듦
const content_kmj = document.createElement("tr");
content_kmj.setAttribute("onclick", `articleDetail(${articles[id].pk})`);
content_kmj.innerHTML = `
<th>${articles[id].pk}</th>
<td>${articles[id].title}</td>
<td>${articles[id].nickname}</td>
<td>${articles[id].created_at}</td>
`;
return content_kmj;
};
const makeButton = (id) => {
// 버튼 생성하기
const button_kmj = document.createElement("button");
button_kmj.classList.add("button_kmj");
button_kmj.dataset.num = id;
button_kmj.innerText = id;
button_kmj.addEventListener("click", (e) => {
Array.prototype.forEach.call(buttons.children, (button) => {
if (button.dataset.num) button.classList.remove("active");
});
e.target.classList.add("active");
renderContent(parseInt(e.target.dataset.num));
});
return button_kmj;
}
const renderContent = (page) => {
// 목록 리스트 초기화
while (contents.hasChildNodes()) {
contents.removeChild(contents.lastChild);
}
// 글의 최대 개수를 넘지 않는 선에서, 화면에 최대 10개의 글 생성
for (let id = (page - 1) * maxContent + 1; id <= page * maxContent && id <= numOfContent; id++) {
contents.appendChild(makeContent(id - 1));
}
};
// 5페이지 넘어가는 경우 6페이지로 넘어가거나 6페이지 이상에서 5페이지 이전으로 넘어올 때 필요한 함수
const goPrevPage = () => {
page -= maxButton;
render(page);
};
const goNextPage = () => {
page += maxButton;
render(page);
};
// 5페이지 넘어갈 때 6페이지쪽으로 넘어가는 버튼과 다시 이전 페이지로 넘어가는 버튼
const prev = document.createElement("button");
prev.classList.add("button_kmj", "prev");
prev.innerHTML = `<ion-icon name="chevron-back-outline"></ion-icon>`;
prev.addEventListener("click", goPrevPage);
const next = document.createElement("button");
next.classList.add("button_kmj", "next");
next.innerHTML = `<ion-icon name="chevron-forward-outline"></ion-icon>`;
next.addEventListener("click", goNextPage);
const renderButton = (page) => {
// 버튼 리스트 초기화
while (buttons.hasChildNodes()) {
buttons.removeChild(buttons.lastChild);
}
// 화면에 최대 10개의 페이지 버튼 생성
for (let id = page; id < page + maxButton && id <= maxPage; id++) {
buttons.appendChild(makeButton(id));
}
// 첫 버튼 활성화(class="active")
buttons.children[0].classList.add("active");
buttons.prepend(prev);
buttons.appendChild(next);
// 이전, 다음 페이지 버튼이 필요한지 체크
if (page - maxButton < 1) buttons.removeChild(prev);
if (page + maxButton > maxPage) buttons.removeChild(next);
};
const render = (page) => {
renderContent(page);
renderButton(page);
};
render(page);
}
- 이건 사실 내가 짠 코드는 아니다
- 하루종일 구글링 한 결과 내가 이해할 수 있는 코드를 가져와서 우리 프로젝트에 맞게 아주 살짝 고친 거다
- 다음에는 백엔드에서 페이지네이션 하는 걸 공부해서 구현해보겠다....!
<좋아요>
- 백엔드
# articles/urls.py
from django.urls import path
from . import views
urlpatterns = [
...
path("like/<int:comment_id>/", views.LikeView.as_view(), name="like_view"),
...
]
- 먼저 url을 설정해주고
# articles/views.py
class LikeView(APIView):
def post(self, request, comment_id):
"""댓글 좋아요 누르기"""
comment = get_object_or_404(Comment, id=comment_id)
if request.user in comment.like.all():
comment.like.remove(request.user)
return Response("dislike", status=status.HTTP_200_OK)
else:
comment.like.add(request.user)
return Response("like", status=status.HTTP_200_OK)
- 좋아요 기능을 함수를 구현했다
- HTTP status가 200으로 똑같기 때문에 프론트에서는 반환되는 텍스트값으로 좋아요와 좋아요 취소를 구현할 것이다
- 프론트엔드
// api.js
...
// 좋아요 누르기
async function likeClick(comment_id) {
const comment = await getComment(comment_id);
let token = localStorage.getItem("access");
let clickLike = document.getElementById(`like-${comment_id}`);
let clickDislike = document.getElementById(`dislike-${comment_id}`);
const response = await fetch(
`${backend_base_url}/api/articles/like/${comment_id}/`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`
}
}
);
if (response.status == 401) {
alert("로그인한 사용자만 좋아요를 누를 수 있습니다");
}
const response_json = await response.json();
//좋아요 하트 색 변경
if (response_json == "like") {
clickLike.setAttribute("style", "display:flex;");
clickDislike.setAttribute("style", "display:none;");
} else if (response_json == "dislike") {
clickLike.setAttribute("style", "display:none;");
clickDislike.setAttribute("style", "display:flex;");
}
}
...
- 이전 프로젝트에서 구현했던 코드를 거의 그대로 가져와서 다시 썼다
- 다만 게시글이 아닌 댓글에 구현되는 것이기 때문에 comment_id를 받아서 백엔드로 보낸다
- html에서 하트 아이콘이 한 번 클릭되면 빨간 하트로 변하고 두 번 클릭되면 빈 하트로 변한다
- 로그인 하지 않은 사용자는 좋아요 누를 수 없기 때문에 alert창을 띄우게끔 했다
// articleDetail.js
...
//좋아요 하트색 세팅
if (token) {
let like = document.getElementById(`like-${comment.id}`);
let dislike = document.getElementById(`dislike-${comment.id}`);
login_user.like_comments.forEach((obj) => {
if (comment.id == obj.id) {
like.setAttribute("style", "display:flex;");
dislike.setAttribute("style", "display:none;");
}
});
}
...
- 처음에 상세페이지를 들어왔을 때 본인이 하트 누른 댓글은 빨간 하트, 누르지 않은 댓글은 빈 하트로 보여져야 하기 때문에 로그인 한 사용자의 경우 serializer를 통해 들어오는 사용자 정보 중 like_comments 필드의 댓글 id를 해당 댓글 id와 비교해서 일치하면 빨간 하트가 보이게끔 했다
- 로그인 하지 않으면 본인인지 아닌지 알 수 없기 때문에 다 빈 하트로 보인다
- js파일에서 만들지 않고 html에서 만들었다면 로그인 하지 않은 사용자한테는 하트 조차 보이지 않게 할 수 있을 것 같아서 약간 UI적으로 아쉬운 부분이 있다
- 하지만 난 백엔드 개발자가 될 것이기 때문에 크게 아쉽진 않다..!!!
<북마크>
- 백엔드
# articles/urls.py
from django.urls import path
from . import views
urlpatterns = [
...
path(
"bookmark/<int:article_id>/", views.BookmarkView.as_view(), name="bookmark_view"
),
...
]
- 좋아요랑 똑같이 urls.py를 작성하고 view.py에서 해당 함수를 만든다
# articles/views.py
class BookmarkView(APIView):
def post(self, request, article_id):
"""게시글 북마크 하기"""
article = get_object_or_404(Article, id=article_id)
if request.user in article.bookmark.all():
article.bookmark.remove(request.user)
return Response("unbookmark", status=status.HTTP_200_OK)
else:
article.bookmark.add(request.user)
return Response("bookmark", status=status.HTTP_200_OK)
- 프론트엔드
// api.js
...
// 북마크 누르기
async function bookmarkClick(article_id) {
const article = await getArticle(article_id);
let token = localStorage.getItem("access");
let clickBookmark = document.getElementById(`bookmark-${article_id}`);
let clickUnbookmark = document.getElementById(`unbookmark-${article_id}`);
const response = await fetch(
`${backend_base_url}/api/articles/bookmark/${article_id}/`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`
}
}
);
if (response.status == 401) {
alert("로그인한 사용자만 북마크 할 수 있습니다");
}
const response_json = await response.json();
// 북마크 버튼 변경
if (response_json == "bookmark") {
clickUnbookmark.setAttribute("style", "display:flex;");
clickBookmark.setAttribute("style", "display:none;");
} else if (response_json == "unbookmark") {
clickUnbookmark.setAttribute("style", "display:none;");
clickBookmark.setAttribute("style", "display:flex;");
}
}
...
- 좋아요 기능과 거의 유사하다
- 좋아요는 요소 안에 요소 안에 요소 안에 요소 막 이런 식으로 태그가 타고타고타고타야 돼서 그냥 백틱으로 js에서 html처럼 구현했지만 북마크는 비교적 간단해서 js에서 html 요소를 불러와서 set 해주는 걸로 했다
// ariticleDetail.js
...
// 북마크 버튼 세팅
async function setBookmarkBtn(article) {
const login_user = await getLoginUser();
if (login_user.id === article.owner.id) {
const articleButtons = document.getElementById("btns");
const updateButton = document.createElement("button");
const deleteButton = document.createElement("button");
updateButton.setAttribute("class", "article-btn btn");
updateButton.setAttribute("type", "button");
updateButton.innerText = "수정하기";
updateButton.setAttribute("onclick", `articleUpdate(${article_id})`);
deleteButton.setAttribute("class", "article-btn btn del-btn");
deleteButton.setAttribute("type", "button");
deleteButton.innerText = "삭제하기";
deleteButton.setAttribute("onclick", `articleDelete(${article_id})`);
articleButtons.appendChild(updateButton);
articleButtons.appendChild(deleteButton);
} else if (token) {
const articleButtons = document.getElementById("btns");
const bookmarkButton = document.createElement("img");
const unbookmarkButton = document.createElement("img");
bookmarkButton.setAttribute("src", "../static/image/bookmark.svg");
bookmarkButton.setAttribute("class", "btn p-0 bookmarkbtn");
bookmarkButton.setAttribute("type", "button");
bookmarkButton.setAttribute("id", `bookmark-${article_id}`);
bookmarkButton.innerText = "북마크 하기!";
bookmarkButton.setAttribute("onclick", `bookmarkClick(${article_id})`);
unbookmarkButton.setAttribute("src", "../static/image/bookmarked.svg");
unbookmarkButton.setAttribute("class", "btn p-0 bookmarkbtn");
unbookmarkButton.setAttribute("type", "button");
unbookmarkButton.setAttribute("id", `unbookmark-${article_id}`);
unbookmarkButton.setAttribute("style", "display:none;");
unbookmarkButton.innerText = "북마크 취소..";
unbookmarkButton.setAttribute("onclick", `bookmarkClick(${article_id})`);
articleButtons.appendChild(bookmarkButton);
articleButtons.appendChild(unbookmarkButton);
let bookmark = document.getElementById(`bookmark-${article_id}`)
let unbookmark = document.getElementById(`unbookmark-${article_id}`)
login_user.bookmarks.forEach((obj) => {
if (article_id == obj.id) {
unbookmark.setAttribute("style", "display:flex;")
bookmark.setAttribute("style", "display:none;")
}
});
}
}
...
- 이게 뭐야 싶겠지만 위에는 로그인 한 사용자와 게시글 작성자가 일치하는 경우에 수정, 삭제 버튼을 넣어주는 코드고 else if문이 북마크와 관련된 코드다
- 위에 수정, 삭제 버튼과 유사한데 좋아요 버튼처럼 북마크 버튼은 누르면 색깔 칠한 북마크 아이콘과 흑백 아이콘을 왔다갔다 한다
예시 이미지를 넣으면 참 좋겠지만....TIL이 밀려서 기력이 딸린다...