하루 하나씩 작성하는 TIL #14
어제 작성하였던 localStorage 코드에 대해 리뷰.
로컬 스토리지는 브라우저 내에서 영구적으로 데이터를 저장하는 데 사용된다.
저장한 데이터는 브라우저를 종료하거나 컴퓨터를 재시작해도 유지된다.
키와 값을 기반으로 배열로 저장한다
다른 분이 제작해주실 페이지에 삽입해야 할 코드이지만, js만 짜놓으면 정상적으로 동작하는지의 여부를 정확하게 알 수 없기 떄문에 간단하게 html틀을 짜놓았다.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8">
<title>영화 리뷰 작성</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<section>
<form class="js-reviewForm">
<div class="left-input">
<input class="user-id-input" type="text" placeholder="사용자 아이디" /><br>
<input class="password-input" type="password" placeholder="비밀번호 (4자리 이상)" />
</div>
<div class="right-input">
<input class="review-input" type="text" placeholder="리뷰를 작성해주세요" />
</div>
<button type="submit">작성하기</button>
</form>
<ul class="js-reviewList"></ul>
</section>
<script src="script.js"></script>
</body>
</html>
class명은 각각 이렇게 준 상태
left right는 원래 각각 좌우로 배치하려다 별로여서 세로로 놨습니다... 어차피 제 코드는 프로젝트에서 js만 사용될 예정이기 때문에 class명 양해 부탁합니다...
const reviewForm = document.querySelector(".js-reviewForm");
const reviewInput = reviewForm.querySelector(".review-input");
const userIdInput = reviewForm.querySelector(".user-id-input");
const passwordInput = reviewForm.querySelector(".password-input");
const reviewList = document.querySelector(".js-reviewList");
//html문서에서 해당 클래스들 가진 요소를 선택하기 위한 변수들
일단, js에서 해당 클래스를 가진 요소를 선택하기 위한 변수들을 선언해준다.
const REVIEWS_LS = "reviews";
//로컬 스토리지에 리뷰 데이터를 저장할 때 사용할 키 값
let reviews = [];
//리뷰 데이터를 담을 배열을 선언
로컬 스토리지는 키 값 쌍 으로 데이터를 저장하기 때문에 키 값을 정해줘야 한다.
reviews는 실제로 데이터를 담을 배열이다.
(꼭 배열이 아니여도 되지만 필자는 배열이 편해서 사용하였다.)
가장 먼저 실행될 순서인 리뷰 작성하기 버튼 클릭 후 폼이 submit 되었을 때의 동작 코드를 짜준다.
//제출되었을 때 호출 함수
function handleSubmit(event) {
event.preventDefault();
//기본 제출 동작 막기. 폼이 서버로 제출되는 것을 방지, js에서 정의한 동작 실행
const currentReview = reviewInput.value;
const currentUserId = userIdInput.value;
const currentPassword = passwordInput.value;
제출 동작을 해줄 handleSubmit 함수를 정의해준다.
event는 js에서 이벤트가 발생하면, 이벤트에 대한 정보를 가지고 있는 객체가 생성이 된다. 이 객체는 해당 이벤트에 대한 다양한 정보와 메서드가 포함되어 있다.
submit과 같은 이벤트의 경우, 폼이 제출되면 페이지가 다시 로드되며 서버로 데이터가 전송이 되는데, 이 동작은 웹페이지를 다시 로드하고 페이지 상태를 변경하는데 사용된다.
하지만 위 코드를 호출하면 기본 동작이 막히게 된다
= 폼이 제출되어도 페이지가 리로드 되지 않고 js에서 정의된 동작만 실행된다.
= 페이지를 새로 고치지 않고도 폼 제출 시 즉시 응답이 가능해진다.
선언된 변수들은 각각 리뷰 내용, id, 비밀번호를 가져와 변수에 할당해준다.
if (currentReview === "" || currentUserId === "" || currentPassword === "") {
alert("모든 필드를 입력하세요.");
return;
}
if (currentPassword.length < 4) {
alert("비밀번호는 4자리 이상이어야 합니다.");
return;
}
입력 받은 값에 대한 유효성 검사도 해준다.
이 과정까지 끝나면, 작성한 리뷰가 하단에 뜨도록 해줘야한다. 하지만 아직 제출 동작에 대한 handleSubmit()함수만 정의해 주었고, 리뷰 표시에 대한 함수를 정의해주지 않았다. 일단 리뷰 표시 함수를 간단하게만 정의해서 이 함수 내가 아닌 외에 정의해준다.
function paintReview() {
}
일단은 submit 버튼 클릭시 하단에 작성한 리뷰가 떠야하니까 이렇게만 정의해주고, 마저 이어서 handleSubmit()을 작성하자면,
나머지 코드들은
paintReview(currentReview, currentUserId, currentPassword);
//입력된 리뷰 내용과 사용자 정보를 기반으로 함수를 호출하여 새로운 리뷰를 화면에 추가
reviewInput.value = "";
userIdInput.value = "";
passwordInput.value = "";
}
현재 입력받은 값을 paintReview를 통해 출력하는 방식으로 마무리를 할 수 있을 거 같다.
출력 후에는 입력 필드의 값을 위와 같이 비워준다.
리뷰가 입력되었으면 로컬 스토리지에 값이 저장되어야한다. 이에 대한 리뷰 저장 함수이다.
function saveReviews() {
localStorage.setItem(REVIEWS_LS, JSON.stringify(reviews));
}
localStorage.setItem(key, value) 함수는 로컬 스토리지에 데이터를 저장하는 데 사용된다. 이 함수는 key와 value 두 가지 매개변수를 사용한다.
REVIEWS_LS는 이전에 정의 된 상수이다.
로컬 스토리지에 저장될 리뷰 데이터의 키, 배열인 reviews를 json 문자열로 변환해준다. 로컬 스토리지에는 문자열 형태의 데이터만 저장이 가능하기 때문이다. 이를 통해 브라우저를 닫았다가 열어도 저장된 리뷰 데이터가 유지된다.
따라서 이 함수는 reviews 배열을 로컬 스토리지에 REVIEWS_LS 키로 저장한다. 이후에 이 데이터를 가져와서 다시 배열로 변환하여 사용할 수 있다.
위에서 submit 후 값을 출력하기 위해 paintReview함수를 틀만 제작해두었었다. 이 함수를 마저 작성해보도록 하겠다.
//리뷰 표시 함수
function paintReview(text, userId, password) {
const li = document.createElement("li");
const delBtn = document.createElement("button");
const updateBtn = document.createElement("button");
const span = document.createElement("span");
const timeSpan = document.createElement("span");
const newId = reviews.length;
//span은 리뷰의 텍스트를 담을 요소, newID는 새로운 리뷰를 배열에 추가할 때 사용할 새로운 id
const currentTime = new Date(); // 현재 시간을 얻음
위 코드에 대해 설명하기 전, 별 거 아니지만 필자가 헷갈려했던 부분.
-> handleSubmmit에선 위에 정의해 두었던 변수들을 가져와 쓰는 것이기 때문에 함수를 호출하여 변수명을 사용하는 것은 문제가 없다.
= 호출 시에 변수 명은 함수 내에서 선언된 매개변수명과 일치할 필요가 없음
이어서 설명을 해보자면, 필자가 원하는 출력되는 리뷰 내용은
새로운 리뷰들을 담을 < li > 요소를 생성
삭제버튼, 수정버튼 생성
리뷰의 텍스트를 담을 < span > 요소를 생성
리뷰 작성 시간을 표시할 < span > 요소를 생성해주고,
새로운 리뷰의 id를 생성해준다. 여기서는 reviews의 길이를 이용하여 새로운 id를 할당하는데, 새로운 리뷰가 배열에 추가될 때 마다 새로운 id를 부여하기 위함이다.
그리고 마지막으로, 현재 시간을 가져오는 Date객체를 생성해준다.
텍스트를 그룹화 하고 리뷰 텍스트를 span요소에 넣어주기 위함이다.
< li >요소는 목록의 항목을 나타내는데에 사용되므로 그 내부에 직접 텍스트를 넣기보다는 < span > 태그를 이용하여 텍스트를 담는 것이 구조상 더 적절하다.
이 코드를 설명하기 전에, 작성된 리뷰에는 리뷰 삭제, 리뷰 수정 버튼이 들어가게 되는데, 그 동작을 수행해줄 함수를 정의해줘야한다.
function updateReview() {
}
function deleteReview(){
}
이렇게 현재 작성 중인 함수 외부에 정의해주고 마저 작성해보겠다.
delBtn.innerText = "리뷰 삭제";
updateBtn.innerText = "리뷰 수정";
delBtn.addEventListener("click", deleteReview);
updateBtn.addEventListener("click", updateReview);
span.innerText = text;
timeSpan.innerText = currentTime.toLocaleString(); // 시간을 문자열로 변환하여 출력
버튼 내 텍스트 넣어주고, 아까 정의해 준 함수들에 이벤트 리스너를 추가해준다. 클릭 이벤트가 발생하면, 위 함수가 수행된다.
매개변수로 받은 text의 값을 span 요소에 설정해준다.
시간을 문자열로 변환, 현재 로컬 시간을 사용해준다.
li.appendChild(span);
li.appendChild(timeSpan); // 시간 추가
li.appendChild(delBtn);
li.appendChild(updateBtn);
//li요소에 각각 요소를 추가하여 화면에 표시
li.id = newId;
//새로 생성된 리뷰의 id 할당
reviewList.appendChild(li);
//새로 생성된 리뷰를 화면에 추가
새로운 리뷰의 텍스트 내용을 담는 < span > 요소를 li 요소의 자식으로 추가
새로운 리뷰의 작성 시간을 나타내는 < span > 요소를 li 요소의 자식으로 추가
리뷰 삭제를 위한 버튼 요소를 li 요소의 자식으로 추가
리뷰 수정을 위한 버튼 요소를 li 요소의 자식으로 추가
새로 생성된 리뷰의 ID를 할당해준다. 이 ID는 리뷰를 식별하는 데 사용된다.
새로 생성된 li 요소를 리뷰 목록을 나타내는 reviewList에 추가
const reviewObj = {
text,
userId,
password,
time: currentTime.getTime() // 현재 시간을 밀리초로 변환하여 저장
};
//새로운 리뷰에 대한 객체 생성
reviews.push(reviewObj);
saveReviews();
}
//리뷰 객체를 배열에 추가
//새로운 리뷰가 추가된 배열을 로컬 스토리지에 저장
// => 새로운 리뷰가 입력되면 화면에 표시되고, 해당 정보가 배열에 추가. 화면을 새로고침해도 유지.
reviewObj에 새로운 리뷰를 저장해준다. 객체로 묶어서 관리함으로서 효율적인 관리가 가능하며 이 객체를 배열에 추가함으로서 여러개의 리뷰를 순차적으로 관리할 수 있다.
이 객체를 reviews배열에 추가한다.
배열의 내용이 추가로 인해 변경되었으므로 saveReviews() 함수를 호출하여 변경된 리뷰 목록을 로컬 스토리지에 저장해준다.
//리뷰 삭제 함수
function deleteReview(event) {
const btn = event.target;
const li = btn.parentNode;
//이벤트가 발생한 요소와 그 부모요소인 li찾기
const password = prompt("비밀번호를 입력하세요:");
if (password !== null && password === reviews[li.id].password) {
reviewList.removeChild(li);
//일치 여부 확인 후 리뷰 목록에서 해당 리뷰 삭제
reviews.splice(li.id, 1);
//배열에서 해당 리뷰 삭제
saveReviews();
//삭제된 리뷰를 로컬 스토리지에 저장
} else {
alert("비밀번호가 일치하지 않습니다.");
}
}
//리뷰 수정 함수(업데이트)
function updateReview(event) {
const btn = event.target;
const li = btn.parentNode;
const password = prompt("비밀번호를 입력하세요:");
if (password !== null && password === reviews[li.id].password) {
const newText = prompt("수정할 리뷰를 입력하세요:", reviews[li.id].text);
if (newText !== null && newText !== "") {
reviews[li.id].text = newText;
//배열에 저장된 해당 리븅의 텍스트를 새로운 텍스트로 업데이트
li.querySelector("span").innerText = newText;
//화면에 해당 리뷰의 텍스트를 새로운 텍스트로 업데이트
saveReviews();
//업데이트된 리뷰를 로컬 스토리지에 저장
} else {
alert("리뷰를 입력하세요.");
}
} else {
alert("비밀번호가 일치하지 않습니다.");
}
}
//로컬 스토리지에서 리뷰 데이터 가져오기
function loadReviews() {
const loadedReviews = localStorage.getItem(REVIEWS_LS);
if (loadedReviews !== null) {
const parsedReviews = JSON.parse(loadedReviews);
//가져온 리뷰 데이터를 json 형식에서 js객체로 변환
parsedReviews.forEach(function (review) {
paintReview(review.text, review.userId, review.password);
});
}
}
//변환 된 리뷰 데이터를 순회하며 각 리뷰를 화면에 표시
//초기화 함수
function init() {
loadReviews();
//웹페이지가 로드될 때 저장된 리뷰를 로드, 화면에 표시
reviewForm.addEventListener("submit", handleSubmit);
}
init();
//페이지 초기화.
남은 부분은 2편으로 가져오겠다.