[Mini Project_자기소개 웹페이지 만들기] Firebase를 활용한 방명록 페이지 CRUD 구현

2024. 12. 24. 22:45·Dev Projects
  • 프로젝트명 : 자기소개 웹페이지 만들기
  • 프로젝트 소개 : 팀 전체의 소개와 각자 팀원의 자기소개 웹 페이지를 만듭니다. 모두들 친해지기 바래 :)

 

역할

  • 방명록 페이지 방명록 조회
  • 방명록 페이지 수정/삭제 전 비밀번호 확인
  • 방명록 수정/삭제
  • 방명록 조회란 bootstrap, css 적용

 

사용하는 기술

  • HTML
  • JavaScript
  • JQuery
  • CSS
  • GitHub
  • Firebase

NoSQL 기본키 필드를 추가해야할까? 

방명록 페이지의 방명록 조회/수정/삭제를 맡게 되어 팀원분이 방명록을 insert하는것까지 구현한 코드를 전달 받았다.

Firebase를 이용하여, 방명록을 입력받고 입력받는 필드는 다음과 같다.

  • postings 방명록 포스팅 컬렉션
    • message 방명록 내용
    • password 삭제/수정 시 사용할 비밀번호
    • username 작성자 이름

내용을 전달 받으니 고민이 되었다.

관계형 데이터베이스에서는 수정/삭제를 위한 기본키가 존재하는데, 우리 테이블에는 기본키가 없잖아?!

기본키를 만들어야겠다고 생각하고 구글링을 해보니 

"NoSQL을 관계형DB처럼 생각하고 사용하지 말라."

 

이 말을 보고 음. 모르겠군. 튜터님께 질문하자~! 하고 바로 질문하러 갔다.

 

NoSQL의 기본키 생성 여부에 대한 결론은,

firebase에서는 document에 대한 고유한 id가 부여되니,

기본키 필드를 따로 생성할 필요 없이 document id를 기본키처럼 사용하면 된다는 답변을 받았다.

document id 예시

 

생각보다 간단 명료하게 궁금증이 해결되어서 좋았다 🤓👍


postings 컬렉션 읽어오기 - 방명록 조회 

postings 컬렉션의 전체 문서를 읽어와서 부트스트랩 카드로 append 해줬다.

 

https://mannakingdom.tistory.com/47

 

[TIL] NoSQL_Firebase 설정/적용하기, Github로 배포하기 (24-12-13)

🤖 사전 캠프 5일차 진행 사항 🤖 웹개발 종합반 4주차 수업4-1 ~ 4-9웹개발 종합반 5주차 수업5-1 ~ 5-9숙제웹개발 종합반 4주차 수업1. Firebase 프로젝트 생성하기더보기1-1. Firebase모바일 및 웹 애

mannakingdom.tistory.com

웹개발 종합반 4주차 수업을 참고해서 코드를 작성했다.

 

일단 getDocs 메서드를 사용하려면 파이어베이스 sdk를 import해야한다.

// Firebase SDK 라이브러리 가져오기
import { getDocs } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";
// postings 컬렉션 읽어오기
        let guestBooks = await getDocs(collection(db, "postings"));
        guestBooks.forEach((book) => {
            let row = book.data();

            let username = row['username'];
            let message = row['message'];
            let id = book.id;

            let temp_html = `
            <div class="myCard card-body shadow-sm rounded p-4">
                <h5 class="card-title text-primary fw-bold">${username}</h5>
                <p class="card-text text-muted">${message}</p>
                <input class="cardId" type="hidden" value="${id}">
                <div class="d-flex justify-content-end">
                    <a href="#" class="cardBtn cardUpdateBtn" data-bs-toggle="modal" data-bs-target="#passwordModal">수정하기</a>
                    <a href="#" class="cardBtn cardDeleteBtn" data-bs-toggle="modal" data-bs-target="#passwordModal">삭제하기</a>   
                </div>
            </div>`;
            $('#guestBook').append(temp_html);
        });

전체 문서를 guestBooks에 담고, forEach로 하나씩 꺼내어 book에 담아 사용했다.

이 때 book의 document id를 추출하는 방법은 다음과 같았다.

let id = book.id;

그리고 비밀번호를 확인할 때마다 해당 비밀번호가 수정/삭제하려는 방명록의 비밀번호가 맞는지 확인해야 하므로,

추출한 id를 hidden으로 추가한다.

<input class="cardId" type="hidden" value="${id}">


비밀번호 입력 Modal 

수정/삭제 버튼을 클릭했을 때, 비밀번호를 입력 받는 modal이 나오도록 구성했다.

아래는 수정/삭제 버튼이다.

<a href="#" class="cardBtn cardUpdateBtn" data-bs-toggle="modal" data-bs-target="#passwordModal">수정하기</a>
<a href="#" class="cardBtn cardDeleteBtn" data-bs-toggle="modal" data-bs-target="#passwordModal">삭제하기</a>

버튼의 onClick 이벤트에서 비밀번호 입력을 받기 위해서 수정/삭제 버튼 동일하게 cardBtn이라는 클래스를 부여하고,

비밀번호 확인 후, 수정/삭제를 구분하기 위해서 각각 cardUpdateBtn, cardDeleteBtn클래스를 부여했다.

 

그리고, 부트스트랩 모달의 data-bs-toggle="modal" data-bs-target="#passwordModal" 속성을 추가해서

클릭 시 비밀번호 모달이 보여지도록 했다.

<!-- 비밀번호 입력 modal -->
    <div class="modal fade" id="passwordModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h1 class="modal-title fs-5" id="exampleModalLabel">비밀번호를 입력하세요.</h1>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <form>
                        <div class="mb-3">
                            <label for="chkPw" class="col-form-label">비밀번호 : </label>
                            <input type="password" class="form-control" id="chkPw">
                        </div>
                    </form>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">닫기</button>
                    <button id="checkPasswordBtn" type="button" class="btn btn-primary">확인</button>
                </div>
            </div>
        </div>
    </div>

비밀번호 확인 

cardBtn 클래스에 비동기 클릭 이벤트 핸들러 작성

// 수정/삭제 버튼 클릭
        $('.cardBtn').on('click', async function () {
            let type;
            if ($(this).hasClass("cardUpdateBtn")) type = 'update';
            else if ($(this).hasClass("cardDeleteBtn")) type = 'delete';

            let docId = $(this).parent().siblings('.cardId').val();

            // 비밀번호 확인
            $('#checkPasswordBtn').on('click', async function () {
                let chkPw = $('#chkPw').val();

                const docRef = doc(db, "postings", docId);
                const docSnap = await getDoc(docRef);

                if (docSnap.data().password == chkPw) {
                    // 수정인 경우
                    if (type == 'update') {
                        $('#editUsername').val(docSnap.data().username);
                        $('#editMessage').val(docSnap.data().message);
                        cardUpdate(docId);
                    }
                    // 삭제인 경우 
                    else if (type == 'delete') {
                        cardDelete(docId);
                    }
                }
                else {
                    alert("비밀번호가 틀립니다.");
                    return;
                }
            });
        });

 

수정/삭제 버튼을 구분하기 위해서 아래와 같이 type을 저장해주었다.

if ($(this).hasClass("cardUpdateBtn")) type = 'update';
else if ($(this).hasClass("cardDeleteBtn")) type = 'delete';

 

cardBtn을 클릭했을 때의 this는 버튼으로 사용하는 <a>태그이기 때문에,

아래 캡쳐의 파란 상자가 this의 부모, 빨간 상자가 this의 부모의 형제이다.

 

방명록 별 document id로 비밀번호를 조회하기 위해서

hidden으로 추가해둔 document id를 아래와 같이 조회하여 변수에 담아 사용한다. 

let docId = $(this).parent().siblings('.cardId').val();

 

그리고 모달 안의 확인 버튼을 클릭했을 때, 비동기 클릭 이벤트 핸들러를 등록한다.

이 이벤트 핸들러 안에서 document id로 문서를 (하나) 조회한다.

const docRef = doc(db, "postings", docId);
const docSnap = await getDoc(docRef);

firebase의 getDoc 메서드를 사용했다.

 

이 때 발생한 오류 💥 💥 💥 💥 💥

더보기
Uncaught SyntaxError SyntaxError: Identifier 'doc' has already been declared at (program)

오류의 원인은 이미 이전에 firebase-firestore에서 import되었기 때문이었다.

중복 import를 방지하기 위해서, 파이어베이스 sdk를 새로 정리해줬다.

import { 
    initializeApp 
} from "https://www.gstatic.com/firebasejs/9.22.0/firebase-app.js";

import { 
    getFirestore, 
    doc, 
    deleteDoc, 
    collection, 
    addDoc, 
    getDocs, 
    getDoc 
} from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";

 

조회 결과인 docSnap의 password와 모달창에서 입력받은 비밀번호가 동일하다면,

수정/삭제 함수로 이동하도록 하였다.

선택한 방명록만 수정/삭제 해야하므로, 각 함수로 이동할 때 매개변수로 document id를 전달했다.

let chkPw = $('#chkPw').val();

if (docSnap.data().password == chkPw) {
	// 수정인 경우
	if (type == 'update') {
    		$('#editUsername').val(docSnap.data().username);
        	$('#editMessage').val(docSnap.data().message);
        	cardUpdate(docId);
	}
	// 삭제인 경우 
	else if (type == 'delete') {
    		cardDelete(docId);
	}
}
else {
	alert("비밀번호가 틀립니다.");
	return;
}

 

방명록 수정의 경우 modal을 사용해야하기 때문에, 모달의 input 태그의 value를 미리 넣은 다음에 cardUpdate함수로 이동하도록 했다.


방명록 삭제 

// 방명록 삭제
        async function cardDelete(docId) {
            const docRef = doc(db, "postings", docId);
            await deleteDoc(docRef);
            alert('삭제 완료');
            window.location.reload();
        }

firebase의 deleteDoc메서드를 사용했다.

docId와 동일한 문서를 삭제해준다.


방명록 수정 

수정 modal은 다음과 같다.

<!-- 수정 modal -->
    <div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h1 class="modal-title fs-5" id="exampleModalLabel">방명록을 수정하세요.</h1>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <form>
                        <div class="mb-3">
                            <label for="editUsername" class="col-form-label">이름:</label>
                            <input type="text" class="form-control" id="editUsername" readonly>
                        </div>
                        <div class="mb-3">
                            <label for="editMessage" class="col-form-label">메시지:</label>
                            <textarea id="editMessage" class="form-control" rows="5" cols="50"></textarea>
                        </div>
                    </form>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">닫기</button>
                    <button id="saveMessageBtn" type="button" class="btn btn-primary">저장하기</button>
                </div>
            </div>
        </div>
    </div>

 

비밀번호 확인 버튼을 클릭했을 때 동작하는 이벤트 핸들러에서 방명록 수정 함수로 이동하기 전에,

방명록 내용(#editMessage)을 넣어놓았기 때문에 수정 함수에서는 val만 읽어서 update해주면 된다.

// 빙명록 수정
        function cardUpdate(docId) {
            $('#editModal').modal('show');
            $('#saveMessageBtn').on('click', async function () {
                let editMessage = $('#editMessage').val();
                const docRef = doc(db, "postings", docId);
                await updateDoc(docRef, {
                    message: editMessage,
                });
                alert('수정 완료');
                window.location.reload();
            });
        }

firebase의 updateDoc메서드를 사용했다.

docId와 동일한 문서의 지정한 필드를 전달하는 값으로 update해준다.


결과 

조회 화면

 

비밀번호 확인 모달

 

방명록 수정 모달


 

https://github.com/mannaKim/Meet-Our-Team

 

GitHub - mannaKim/Meet-Our-Team

Contribute to mannaKim/Meet-Our-Team development by creating an account on GitHub.

github.com

 

저작자표시 비영리 변경금지 (새창열림)

'Dev Projects' 카테고리의 다른 글

[Spring Data JPA_일정 관리 앱 Develop] 필수 & 도전 기능 구현 기록 🖋️  (1) 2025.02.13
[Spring JDBC_일정 관리 앱 만들기] 회고 및 트러블 슈팅  (1) 2025.02.04
[Java Project_키오스크 과제] 도전 기능 구현 및 트러블 슈팅  (0) 2025.01.20
[Java Project_키오스크 과제] 필수 기능 구현 및 단계별 설계  (0) 2025.01.15
[Mini Project_자기소개 웹페이지 만들기] Firestore 데이터 정렬(orderBy)과 HTML 스크립트 분리로 코드 관리 효율화  (1) 2024.12.27
'Dev Projects' 카테고리의 다른 글
  • [Spring JDBC_일정 관리 앱 만들기] 회고 및 트러블 슈팅
  • [Java Project_키오스크 과제] 도전 기능 구현 및 트러블 슈팅
  • [Java Project_키오스크 과제] 필수 기능 구현 및 단계별 설계
  • [Mini Project_자기소개 웹페이지 만들기] Firestore 데이터 정렬(orderBy)과 HTML 스크립트 분리로 코드 관리 효율화
기만나🐸
기만나🐸
공부한 내용을 기록합시다 🔥🔥🔥
  • 기만나🐸
    기만나의 공부 기록 🤓
    기만나🐸
  • 전체
    오늘
    어제
    • ALL (147)
      • TIL (Today I Learned) (56)
      • Dev Projects (15)
      • Algorithm Solving (67)
        • Java (52)
        • SQL (15)
      • Certifications (8)
        • 정보처리기사 실기 (8)
  • 인기 글

  • 태그

    join
    websocket
    시뮬레이션
    백트래킹
    다이나믹프로그래밍
    mysql
    java
    HTML
    Firebase
    bootstrap
    jwt
    Subquery
    jQuery
    그리디
    DFS
    GROUP BY
    BFS
    완전탐색
    programmers
    javascript
    백준
    sql
    greedy
    프로그래머스
    BOJ
    dp
    jpa
    CSS
    Google Fonts
    자료구조
  • 최근 글

  • 최근 댓글

  • hELLO· Designed By정상우.v4.10.3
기만나🐸
[Mini Project_자기소개 웹페이지 만들기] Firebase를 활용한 방명록 페이지 CRUD 구현
상단으로

티스토리툴바