오늘은 어제에 이어서 방명록에 수정버튼을 추가했고 함부로 삭제 및 수정하지 못하게 간단하게 비밀번호도 만들었다.
https://fe-jogha.tistory.com/10
// 입력된 값 가져오기
const name = document.getElementById("name").value;
const message = document.getElementById("message").value;
const password = document.getElementById("password").value;
try {
// Firestore에 데이터 추가
await addDoc(collection(db, "guestbook"), {
name: name,
message: message,
password: password,
timestamp: Number(new Date()) // 등록 순으로 정렬하기 위해
});
window.alert("등록 성공");
// 페이지 새로고침
window.location.reload();
} catch (error) {
// 오류 메시지 표시
console.error("Error adding document: ", error);
window.alert("방명록을 제출하는 동안 오류가 발생했습니다.");
}
});
우선 비밀번호 먼저 만들어야 겠다고 생각해서 name, massage 폼에 password를 firestore에 데이터를 추가했다
// 삭제 버튼 클릭 이벤트 처리
document.addEventListener("click", async (event) => {
if (event.target.classList.contains("delete-btn")) {
const promptPassword = window.prompt("삭제하시려면 비밀번호를 입력하세요.",);
const docId = event.target.getAttribute("data-doc-id");
const userInfo = await getDoc(doc(db, "guestbook", docId));
const userPassword = userInfo.data().password;
if (promptPassword == userPassword) {
console.log("Deleting document with ID:", docId); // docId 출력
try {
// Firestore에서 문서 삭제
await deleteDoc(doc(db, "guestbook", docId));
// 삭제한 항목 제거
event.target.parentElement.remove();
console.log("Document deleted successfully."); //삭제 성공 메시지 출력
} catch (error) {
console.error("Error deleting document: ", error);
window.alert("방명록 항목을 삭제하는 동안 오류가 발생했습니다.");
}
} else {
window.alert("잘못 입력하셨습니다.");
}
}
});
그리고 prompt로 사용자에게 비밀번호를 입력받고 firestore에 있는 password와 동등 연산자를 이용해서 일치하면
기존에 있던 코드를 실행하게끔 했다.
// Firebase에서 방명록 항목 가져오기
async function fetchGuestbookEntries() {
const querySnapshot = await getDocs(
query(collection(db, "guestbook"), orderBy("timestamp", "desc"))
);
// querySnapshot.forEach((doc) => {
// const entry = doc.data();
// displayGuestbookEntry(doc.id, entry); // 문서 ID도 전달
// });
for (const doc of querySnapshot.docs) { // 비동기 배열 순회
const entry = doc.data();
displayGuestbookEntry(doc.id, entry);
}
}
수정버튼을 만들기 전에 어제 만든 코드를 전체적으로 확인해봤고 fetchGuestbookEntries()는 비동기 함수안에
forEach() 메서드는 동기식 처리 메서드를 사용한 것을 확인 했다. 비동기 함수내에 동기식 처리 메서드를 사용하면
해당 매서드는 비동기 함수여도 동기적으로 작동한다. 이유는 forEach() 메서드가 콜백 함수를 동기적으로 실행하기 때문인데 각 요소에 대한 작업은 순차적으로 실행 되기 때문이다. 그래서 비동기적으로 배열을 순회하려면 for of 문 또는 for문과 함께 비동기 작업을 수행 할 수 있는 구문을 사용했다.
// 방명록 항목을 화면에 표시
function displayGuestbookEntry(docId, entry) {
const guestbookEntriesDiv = document.getElementById("guestbook-entries");
const entryDiv = document.createElement("div"); // (1)
entryDiv.classList.add("guestbook-entry");
entryDiv.innerHTML = `
<p><strong>${entry.name}</strong>: ${entry.message}</p>
<button class="edit-btn" data-doc-id="${docId}">수정</button>
<button class="delete-btn" data-doc-id="${docId}">삭제</button>
`;
guestbookEntriesDiv.appendChild(entryDiv);
}
// 수정 버튼 클릭 이벤트 처리
document.addEventListener("click", async (event) => {
if (event.target.classList.contains("edit-btn")) {
const docId = event.target.getAttribute("data-doc-id");
const parentDiv = event.target.parentElement; // 수정 버튼의 부모 요소인 div 가져오기
const messageSpan = parentDiv.querySelector("p"); // 해당하는 부모요소에서 메시지 <p> 요소
const name = messageSpan.textContent.split(": ")[0];
const promptPassword = window.prompt("비밀번호를 입력하세요.",);
const userInfo = await getDoc(doc(db, "guestbook", docId));
const userPassword = userInfo.data().password;
if (promptPassword == userPassword) {
const newMessage = window.prompt("메시지를 입력하세요:",);
if (newMessage !== null) {
try {
// Firestore에서 문서 업데이트
await updateDoc(doc(db, "guestbook", docId), {
message: newMessage
});
messageSpan.textContent = `${name}: ${newMessage}`;
window.alert("메시지가 업데이트되었습니다.");
} catch (error) {
console.error("Error updating document: ", error);
window.alert("메시지를 업데이트하는 동안 오류가 발생했습니다.");
}
}
} else {
window.alert("비밀번호를 잘못 입력하셨습니다.");
}
}
});
message의 값을 수정하기 위해 구글링을 통해 알아본 결과 Node: parentElement property를 이용해서 // 방명록 항목을 화면에 표시에 (1)에 해당하는 div를 가져올 수 있었고 querySelector로 <p> 요소에 있는 name과 message를 가지고 왔다.
split()메서드를 이용해서name을 선언하고 삭제버튼 기능 처럼 prompt를 이용해서 비밀번호를 입력 받고 일치하면
updateDoc을 이용해서 name: message 형식으로 메세지를 업데이트 할 수 있도록 했다.
여기까지의 결과물이다.
위 결과물에서 마음에 안들었던 점은
- 방명록에 표시되는 형식이 이름 : 메시지인 것
- 비밀번호를 입력하는 prompt 창과 수정하는 메시지 또한 prompt를 이용해서 수정을 하려면 창이 2개가 뜨는 것
- 코드 내에서는 수정버튼 클릭 이벤트를 처리하는데 있어서 name은 어차피 변경하지 않는데 parentElement property를 이용해서 name과 message를 둘 다 가지고 온다는 것
창이 2개인 것은 사용자가 불편할 것 같다고 느꼈고 첫 번째와 세 번째는 연관성이 있고 시각적으로도 안 좋고 성능적으로개선이 필요하다고 생각했다.
// // 방명록 항목을 화면에 표시
// function displayGuestbookEntry(docId, entry) {
// const guestbookEntriesDiv = document.getElementById("guestbook-entries");
// const entryDiv = document.createElement("div");
// entryDiv.classList.add("guestbook-entry");
// entryDiv.innerHTML = `
// <p><strong>${entry.name}</strong>: ${entry.message}</p>
// <button class="edit-btn" data-doc-id="${docId}">수정</button>
// <button class="delete-btn" data-doc-id="${docId}">삭제</button>
// `;
// guestbookEntriesDiv.appendChild(entryDiv);
// }
// 방명록 항목을 화면에 표시
function displayGuestbookEntry(docId, entry) {
const guestbookEntriesDiv = document.getElementById("guestbook-entries");
const entryDiv = document.createElement("div");
entryDiv.classList.add("guestbook-entry");
entryDiv.innerHTML = `
<div class="guestbook-container">
<div class="nameMessage">
<p class="userName"><strong>${entry.name}</strong><br></p>
<p class="userMessage">${entry.message}</p>
</div>
<div class="buttons">
<button class="edit-btn" data-doc-id="${docId}">수정</button>
<button class="delete-btn" data-doc-id="${docId}">삭제</button>
</div>
</div>
`;
guestbookEntriesDiv.appendChild(entryDiv);
}
주석 처리된 부분을 보면 <p><strong>${entry.name}</strong>: ${entry.message}</p>가 한 줄에 있어서
수정 버튼에 split을 썼는데 그냥 p태그로 두 줄 작성 하는게 성능적으로 그리고
이런 식으로 표현 되는 것이 시각적으로 더 직관적일 것 같아서 css와 div를 이용해서 위와 같이 만들었다
하지만 위 코드처럼 <div>로 감싸고 p태그를 두 줄로 만들어서 기존에 있던 코드를 전체적으로 수정 했어야 했다.
우선 삭제 버튼 클릭 이벤트 처리에서
// 삭제 버튼 클릭 이벤트 처리
/* 이전 코드와 동일*/
// 삭제한 항목 제거
// event.target.parentElement.remove();
// event.target.parentElement.parentElement.remove();
event.target.closest(".guestbook-entry").remove();
/* 이전 코드와 동일*/
});
삭제한 방명록을 제거 해주는 event.target.parentElement.remove();가 div에 한번 더 감싸져서
event.target.parentElement.parentElement.remove();로 고쳤는데 이 코드는 직관적으로 다른 사람이 이해하기 힘들겠다고 생각해서 가장 가까운 부모요소를 찾는 메서드인 event.target.closest() 메서드를 이용했다
위와 같이 event.target.closest()로 변경하면 장점이 첫 번째로 다른 사람이 코드의 의도를 더 잘 이해 가능하다는 점과
DOM구조의 변경에 상관없이 유연하게 동작해서 코드의 가독성과 유지보수성 측면에서 장점을 가진다.
// 수정 버튼 클릭 이벤트 처리
document.addEventListener("click", async (event) => {
if (event.target.classList.contains("edit-btn")) {
const docId = event.target.getAttribute("data-doc-id");
const parentDiv = event.target.closest(".guestbook-entry");
const messageParagraph = parentDiv.querySelector(".userMessage"); // (1)
const promptPassword = window.prompt("비밀번호를 입력하세요.");
const userInfo = await getDoc(doc(db, "guestbook", docId));
const userPassword = userInfo.data().password;
if (promptPassword == userPassword) {
// 새로운 textarea 생성 (2)
const textarea = document.createElement("textarea");
textarea.placeholder = "수정할 메시지를 입력하세요";
// 저장 버튼 생성
const saveButton = document.createElement("button");
saveButton.textContent = " 저장";
saveButton.classList.add("save-btn");
// textarea와 저장 버튼을 감싸는 div 생성 (3)
const textareaWrapper = document.createElement("div");
textareaWrapper.classList.add("textarea-wrapper");
textareaWrapper.appendChild(textarea);
textareaWrapper.appendChild(saveButton);
// 기존에 있던 메시지 숨기고 수정 영역을 추가함 (4)
messageParagraph.style.display = "none";
parentDiv.appendChild(textareaWrapper);
// 저장 버튼 클릭 이벤트 처리 (5)
saveButton.addEventListener("click", async () => {
const newMessage = textarea.value;
try {
// Firestore에서 문서 업데이트
await updateDoc(doc(db, "guestbook", docId), {
message: newMessage
});
// 업데이트된 메시지를 화면에 반영 (6)
messageParagraph.textContent = newMessage;
messageParagraph.style.display = ""; // p 태그 보이게 함
textareaWrapper.remove(); // 수정 영역 제거
window.alert("메시지가 업데이트되었습니다.");
} catch (error) {
console.error("Error updating document: ", error);
window.alert("메시지를 업데이트하는 동안 오류가 발생했습니다.");
}
});
} else {
window.alert("비밀번호를 잘못 입력하셨습니다.");
}
}
});
수정된 수정 버튼 클릭 이벤트 처리이다
첫 번째로 const parentDiv를 event.target.parentElement; 에서 event.target.closest(".guestbook-entry")로 변경했다
두 번째로 const messageSpan = parentDiv.querySelector("p"); 에서 name과 message 두 개를 가져왔었는데
(1) 에 있는 코드로 name을 가져오지 않고 p class = "userMessage"로 클래스를 선언하고 message만 가져왔다.
(2)
다음으로 수정할 메세지를 prompt로 입력 받아서 비밀번호 입력창과 수정할 메시지 입력창 2개가 연달아 뜨는 것이다
다른 사이트의 댓글 수정 기능을 찾아 본 결과 수정할 때 대부분 textarea 태그를 사용하는 것을 보고 textarea와 버튼을
만들었다.
(3) 시각적으로 꾸미기 위해 div로 감싸주었다
(4) 메시지를 수정하는 중에 기존에 있던 메시지를 숨겨주었다.
(5) newMessage를 textarea.value에 넣어주고 updateDoc를 이용해서 업데이트 했다.
(6) 수정된 메시지를 표시하고 textarea를 제거했다.
최종결과
이름
메시지
형식으로 방명록이 등록 되었고 메시지를 수정할 때 promt 창에서 textarea로 바꾼 결과
코드의 가독성과 유지보수성 측면 그리고 시각적으로 더 좋아졌다.
그리고 수정 기능이 추가되어 방명록으로 간단한 CRUD를 구현했다.
Create (생성)
- 방명록을 생성하는 기능
- 사용자가 폼을 작성하고 제출하면, 이름(name), 메시지(message), 비밀번호(password)를 포함한 새로운 방명록 항목이 Firestore에 추가
- 폼을 작성하고 제출하는 부분은 JavaScript의 이벤트 핸들러를 통해 처리
Read (읽기)
- 저장된 방명록 항목을 조회하는 기능
- 페이지가 로드될 때(fetchGuestbookEntries 함수가 호출될 때) Firebase에서 방명록 항목을 가져와 화면에 표시
- 가져온 항목은 displayGuestbookEntry 함수를 통해 각각의 게시물로 표시
Update (수정)
- 기존의 방명록 항목을 수정하는 기능
- "수정" 버튼을 클릭하면 해당 항목의 비밀번호를 확인하고, 맞으면 해당 항목의 메시지를 수정할 수 있는 텍스트 영역과 저장 버튼이 생성
- 저장 버튼을 클릭하면 Firebase의 문서를 업데이트하고, 화면에 수정된 메시지를 반영
Delete (삭제)
- 방명록 항목을 삭제하는 기능
- "삭제" 버튼을 클릭하면 해당 항목의 비밀번호를 확인하고, 맞으면 Firebase에서 해당 문서를 삭제
'TIL' 카테고리의 다른 글
TIL - 블로그 만들기 모달창 구현 및 블로그 글 목록 선입선출로 만들기 (2) (0) | 2024.04.21 |
---|---|
TIL - JS와 Firestore 이용해서 블로그 만들기 - 탭 전환 (1) (0) | 2024.04.19 |
TIL - Git의 필요성과 prepend()를 사용하면서 생긴 문제 발견과 해결 (1) | 2024.04.18 |
TIL - 간단한 Todo list 만들어서 CRUD기능 구현 (0) | 2024.04.17 |
TIL - firebase와 JS를 이용해서 방명록 구현 (0) | 2024.04.15 |