Firebase 배우기 14 – Firestore Database 수정(Update) 하기

Firebase 배우기 14 – Firestore Database 수정(Update) 하기

선행지식

앞선 글에서 키워드 검색을 통해 특정한 필드에서 해당 키워드에 해당하는 레코드를 찾는 코드를 만드는 과정을 소개했었고,

https://blog.naver.com/mythee1/223494445392

이렇게 검색된 결과들에 대하여 선택적으로 추가 동작을 진행하기 위한 메뉴 버튼들을 다음과 같이 추가하는 것과, “삭제” 버튼 선택으로 해당 anstj(=레코드)를 컬렉션(=DB)에서 삭제하는 방법을 다음 글에서 소개한 바 있었다.

https://blog.naver.com/mythee1/223498786503

레코드 데이터 수정 (Update)

이번 글에서는 이렇게 키워드 검색된 레코드에 “수정” 버튼을 이용하여 해당 레코드의 데이터를 Update 하는 방법을 소개한다.

나는 특정 레코드를 수정하는 과정에서 가능하면 기존의 데이터를 화면에 표시하고, 이를 보면서 새로운 데이터를 입력하고 싶었고, 특히 문장이 길 수도 있는 설명형 자료나, 연속적인 내용들을 계속해서 추가하는 방식으로 사용해야 하는 필드들은 최대한 기존 데이터를 활용하여 편집하거나, 기존 데이터에 새로운 내용을 단순히 추가하는 방식으로 수정하고자 했다.

이 때문에, 최대한 기존 필드 데이터를 가져와 화면에 표시하고, 편집하는 것이 필요했는데, 결과적으로 기존 키워드 검색 화면 내에서 이를 처리하기는 어렵다는 판단이 들었다.

그래서, 다음과 같은 버튼 메뉴가 선택되는 경우,

다음과 같은 handler를 이용해서 새로운 페이지로 이동하되, 해당 문서의 번호를 id 파라미터로 url에 추가하여 해당 페이지로 이동했다.

그리고 다음과 같이 이동한 새로운 페이지에서 id를 먼저 전역변수로 선언해 주고,

이 변수에 키워드 검색 페이지에서 URL과 함께 전달된 id 파라미터 값을 추출하여 저장해 주었다.

그와 동시에 해당 id의 기존 문서의 데이터를 다음 형식으로 편집이 가능하게 input 모드로 설정한 form을 이용하여

다음과 같은 방식으로 표시해 주었다.

결과적으로 화면에는 다음과 같은 형태로 표시되며, 기존 값들이 편집 가능한 상태로 표시된다.

또한 기존에 등록된 사진도 함께 표시되며, 새로운 파일로 대체할 수 있도록 파일 선택 창도 함께 표시된다.

그리고 위의 화면에서 “수정 확인” 버튼의 선택은 다음 코드에 해당되는데,

이 버튼이 눌려지는 경우 다음과 같은 핸들러가 작동하게 되는데, 사진 파일까지 수정되는지를 판단하여 Update할 항목을 결정하게 되는데,

실제 사진 파일이 갱신되는 경우에는 새로운 파일을 storage에 먼저 저장하고, Firebase의 스토리지에 불필요한 파일이 쌓이지 않도록 기존 사진 파일은 storage에서 삭제하는 과정이 필요했다. 또한 Storage에 저장되는 사진 파일이 (여러 레코드가 중복되는 이름을 사용하게 되어 발생하는 오류를 피하기 위해) 유일한 이름을 가질 수 있도록, 파일 이름을 timestamp를 이용하여 변경해 주는 과정이 필요했다.

이렇게 상대적으로 시간이 많이 걸리기도 하고, url 정보의 변경도 필요한 사진 파일의 기록 및 기존 파일 삭제가 완료된 이후, 나머지 정보들의 update는 다음 함수에서 이루어지도록 구성하게 되었다.

이렇게 작성한 코드들의 동작을 확인하기 위한 시험용 목적으로 다음 화면에서 표시한 부분들처럼 수정하고

수정 확인을 누른 후, 해당 키워드로 다시 검색을 해보면 해당 레코드가 다음과 같이 동작 확인을 위한 시도한 대로 잘 변경된 것을 확인할 수가 있다.

참고로 이렇게 완성된 Update 페이지의 소스코드는 다음과 같다. 이 코드의 작성에는 Copilot을 활용했고, firebase 접속 정보는 firebaseConfig.js 파일에 별도 보관했다.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>키워드 검색후 데이타 수정</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<script src="https://www.gstatic.com/firebasejs/8.6.5/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.6.5/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.6.5/firebase-firestore.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.6.5/firebase-storage.js"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<script src="/firebaseConfig.js"></script>
</head>
<body>
<div class="container">
<form id="editForm" class="m-3">
<input type="text" id="title" name="title" class="form-control mb-3" placeholder="상품명">
<input type="text" id="price" name="price" class="form-control mb-3" placeholder="가격">
<textarea id="content" name="content" rows="4" class="form-control mb-3" placeholder="상품설명"></textarea>
<textarea id="remark" name="remark" rows="4" class="form-control mb-3" placeholder="참고노트"></textarea>
<img id="pimageDisplay" style="width: 180px; height: 180px;">
<input type="file" id="pimage" name="pimage" class="form-control mb-3">
<input type="submit" value="수정 확인" class="btn btn-primary" style="background-color: orange;">
</form>
</div>
<script>
var db = firebase.firestore();
var storage = firebase.storage();
var id; // id 변수를 전역 범위에서 선언합니다.
$(document).ready(function() {
var params = new URLSearchParams(window.location.search);
      id = params.get('id'); 
      db.collection('products').doc(id)
.get()
.then((doc) => {
if (doc.exists) {
var data = doc.data();
$('#editForm #title').val(data.상품명);
$('#editForm #price').val(data.가격);
$('#editForm #content').val(data.상품설명);
$('#editForm #remark').val(data.참고노트);
$('#editForm #pimageDisplay').attr('src', data.사진);
} else {
            console.log("No such document!");
}
}).catch((error) => {
          console.log("Error getting document:", error);
});
});
$('#editForm').submit(function(e) {
        e.preventDefault();
var timestamp = new Date().getTime();
var title = $('#title').val();
var price = $('#price').val();
var content = $('#content').val();
var remark = $('#remark').val();
var file = $('#pimage').get(0).files[0];
var fileName = 'image_' + timestamp + '.jpg'
if (file) {
var storageRef = storage.ref();
var fileRef = storageRef.child('products/' + fileName);
          
          fileRef.put(file).then((snapshot) => {
            snapshot.ref.getDownloadURL().then((url) => {
var oldImageName = $('#editForm #pimageDisplay').attr('src');
var oldImageRef = storage.ref('products/' + oldImageName);
                oldImageRef.delete().then(() => {
                    console.log("Old image successfully deleted!");
}).catch((error) => {
                    console.error("Error deleting old image: ", error);
});
updateProduct(title, price, content, remark, url);
});
});
} else {
updateProduct(title, price, content, remark);
}
})
function updateProduct(title, price, content, remark, imageUrl) {
var product = {
상품명: title,
가격: price,
상품설명: content,
참고노트: remark,
날짜: new Date()
};
if (imageUrl) {
        product.사진 = imageUrl;
}
      db.collection('products').doc(id).update(product).then(() => {
        console.log("Document successfully updated!");
setTimeout(()=>window.location.href = "/readit.html",500)
}).catch((error) => {
        console.error("Error updating document: ", error);
});
}
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>
</html>