JSON.stringify
오늘은 홈페이지에 ToDo 리스트 기능을 넣으려한다.
저번과 느낌은 비슷하겠지만, 다른 점이 있다면 이번은 말그대로 리스트 li 인 점이다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="css/style.css" />
<title>Momentum</title>
</head>
<body>
<form class="hidden" id="login-form">
<input
required
maxlength="15"
type="text"
placeholder="what is your name?"
/>
<input type="submit" value="Log in"></input>
</form>
<h2 id="clock">00:00:00</h2>
<h1 id="greeting" class="hidden"> i'm hiding !</h1>
<form id="todo-form">
<input type="text" placeholder="Write a To Do and press the Enter" required>
</form>
<ul id="todo-list"></ul>
<div id="quote">
<span></span>
<span></span>
</div>
<script src="js/greetings.js"></script>
<script src="js/clock.js"></script>
<script src="js/quotes.js"></script>
<script src="js/background.js"></script>
<script src="js/todo.js"></script>
</body>
</html>
Html에 ToDo 리스트 기본 세팅을 해준다.
이번 차이점은 todo-list를 ul 로 묶었고 안에 인자가 li로 생길 것이라는 점이다.
이 말은 JS에서 Html로 ul 안에 li를 추가시켜주는 작업을 한다는 뜻이다.
const toDoForm = document.getElementById("todo-form");
const toDoInput = toDoForm.querySelector("input");
const toDoList = document.getElementById("todo-list");
function paintToDo(newToDo) {
const li = document.createElement("li");
const span = document.createElement("span");
span.innerText = newToDo;
li.appendChild(span);
toDoList.appendChild(li);
}
function handleToDoSubmit(event) {
event.preventDefault();
const newToDo = toDoInput.value;
toDoInput.value = "";
paintToDo(newToDo);
}
toDoForm.addEventListener("submit", handleToDoSubmit);
toDoForm을 submit 시, handleToDoSubmit 가 실행되도록 하였다.
그 다음, 삭제 버튼을 추가시킬 것을 생각하여 사용자가 작성하는 값인
newToDo를 바로 li 안에 넣는 것이 아닌, span 안에 넣기로 하였다.
리스트 생성 뿐만 아니라 삭제 버튼을 만들어 누르면, 해당 리스트가 삭제되도록 만들어보자.
const toDoForm = document.getElementById("todo-form");
const toDoInput = toDoForm.querySelector("input");
const toDoList = document.getElementById("todo-list");
function deleteToDo(event) {
const li = event.target.parentElement;
li.remove();
}
function paintToDo(newToDo) {
const li = document.createElement("li");
const span = document.createElement("span");
span.innerText = newToDo;
const button = document.createElement("button");
button.innerText = "❌";
button.addEventListener("click", deleteToDo);
li.appendChild(span);
li.appendChild(button);
toDoList.appendChild(li);
}
function handleToDoSubmit(event) {
event.preventDefault();
const newToDo = toDoInput.value;
toDoInput.value = "";
paintToDo(newToDo);
}
toDoForm.addEventListener("submit", handleToDoSubmit);
같은 li 안에 삭제 버튼을 button으로 지정하여 ❌ 이모지 텍스트로 넣었다.
❌ 클릭 시, 해당 리스트가 삭제되도록 하는 것이다.
따라서 ❌가 클릭되었다는 것을 인지해야하기 때문에 addEventListener로 click 이벤트를 받아준다.
그리고 deleteToDo 를 만들어 event 중 target 기능을 이용한다.
target 기능 중 parentElement을 사용하면 부모를 알 수 있다.
따라서 버튼을 클릭 시, button의 부모가 되는 li 자체를 삭제할 수 있게 만들어준다.
현재 단계까지 완성하면 리스트 작성과 삭제가 가능하지만, 브라우저 저장은 불가능하다.
새로고침 시, 리스트의 데이터가 날라가 아예 작성되기 전 초기화 상태로 돌아간다.
여기에서 저번 시간에 썼던 localStorage 를 이용하도록 한다.
const toDoForm = document.getElementById("todo-form");
const toDoInput = toDoForm.querySelector("input");
const toDoList = document.getElementById("todo-list");
function deleteToDo(event) {
const li = event.target.parentElement;
li.remove();
}
const toDos = [];
function saveToDos() {
localStorage.setItem("todos", JSON.stringify(toDos));
}
function paintToDo(newToDo) {
const li = document.createElement("li");
const span = document.createElement("span");
span.innerText = newToDo;
const button = document.createElement("button");
button.innerText = "❌";
button.addEventListener("click", deleteToDo);
li.appendChild(span);
li.appendChild(button);
toDoList.appendChild(li);
}
function handleToDoSubmit(event) {
event.preventDefault();
const newToDo = toDoInput.value;
toDoInput.value = "";
toDos.push(newToDo);
paintToDo(newToDo);
saveToDos();
}
toDoForm.addEventListener("submit", handleToDoSubmit);
리스트들은 배열로 만들어져야하기 때문에 따로 리스트들을 넣어둘 toDos 함수를 만든다.
이 toDos에 작성되는 값인 newToDo를 push 하여 넣어주도록 한다.
하지만 여기에서 문제가 생긴다.
바로 localStorage는 텍스트만 저장 가능하다.
즉, array(배열)을 저장할 수 없다는 것이다.
따라서 이를 임시적으로 변환과 저장해줄 함수를 따로 만들도록 한다.
saveToDos로 만들었고 여기에선 localStorage로 넣어주는 역할만 한다.
원래면 localStorage.setItem("todos", toDos); 으로 저장이 될 것이다.
하지만 우리는 이렇게 코딩 시, localStorage 안에 string 형태가 아닌 배열 식으로 넣게된다.
따라서 문제가 생기는 이 부분을 변환해줘야한다.
답은 string으로 변환해주는 JSON.stringify 사용하는 것이다.
그러면 값을 입력 시, 실제로는 stirng이지만 [”a”, “b”]배열처럼 보이게 된다.
아직까지는 새로고침을 해도 toDos가 localStorage에 남아있지만, 화면에 나타나진 않는다.
JSON.parse
✅ 저번에 공부했던 JSON.stringify으로 저장한 string 값은 string data type 으로 저장이 된다.
(예: "[a,b,c,d,e]")
✅ 우리가 원하는 자바스크립트에서 사용 가능한 형태의 완벽한 array 형태는 아니기때문에 다시 온전한 형태의 array로 꺼낼 필요가 있다.
stringify 된 것을 다시 array로 꺼내려면 parse를 써야한다.
JSON.parse()를 통해 string data type을 우리가 원하는 object 형태로 꺼낼 수 있다.
✅ 근데 이 Object는 Array 같이 바뀌었었다. 즉 index를 통해 value를 access할수 있다.
예: "[a,b,c,d]" (string) => a, b, c, d;
array[0] = a; array[1] = b; array[2] = c; array[3] = d
✅ array 형태가 된 값을 parsedToDos 라는 const variable 에 넣어놨다.
✅ 이 상태에서 parsedToDos 는 array 형태라고 가정했을때 .forEach() 라는 function 을 사용할 수 있다.
forEach는 그냥 단순히 array 에 들어있는 모든 값을 iterate (순찰(?)) 할수 있는 function 이다.
✅ 즉 index 0 부터 index 마지막 까지 돌면서 그 값들을 item 라는 곳 또는 element에 저장이 되는 것이다.
ex)
Array = [ a, b, c, d]
Array.foreach( (item) => console.log(item))
// output:
a
b
c
d
✅ 이 말은 즉, 우리는 forEach를 통해 element 각각 하나에 해당하는 기능을 줄 수 있다는 뜻이다.
function sayHello(item) {
console.log("this is the turn of", item);
}
const savedToDos = localStorage.getItem(TODOS_KEY);
if (savedToDos !== null) {
const parsedToDos = JSON.parse(savedToDos);
console.log(parsedToDos);
parsedToDos.forEach(sayHello);
}
따라서 이렇게 코딩 시, array에 a, b, c, d를 넣었다면
// output:
this is the turn of a
this is the turn of b
this is the turn of c
this is the turn of d
array 의 item 들에 대해 한 개의 function 을 실행시킬 수 있다.
추가로 arrow function을 소개하겠다.
위에서 사용한 sayHello나 또 다른 function을 매번 만들기엔 번거롭기 때문에 일종의 shortcut 같은 역할을 하기 위해 사용한다.
기본 형태는 다음과 같다.
() = >
쉽게 설명하기 위해 위에 코딩을 arrow function을 이용하여 변경해보겠다.
const savedToDos = localStorage.getItem(TODOS_KEY);
if (savedToDos !== null) {
const parsedToDos = JSON.parse(savedToDos);
console.log(parsedToDos);
parsedToDos.forEach((item) => console.log("this is the turn of", item));
}
sayHello처럼 따로 새로운 function을 정의하지 않아도 같은 역할을 한다.
arrow function을 사용 시, 훨씬 코딩이 깔끔해지고 짧아지는 것을 볼 수 있다.
사실 더 깔끔한 정리는 아직 남아있다.
위에서 선언했던 paintToDo를 저 자리에 넣으면 되기때문이다.
if (savedToDos !== null) {
const parsedToDos = JSON.parse(savedToDos);
parsedToDos.forEach(paintToDo);
}
입력하는 a,b,c,d 와 같은 item 들을 매번 paintToDo하는 개념이다.
아직까지 새로고침을 하면 새로 추가시키는 newToDo 들은 화면에 잘 나타나지만
새로고침을 하고 새로 newToDo를 push시키면 기존 데이터는 없어지고 덮어쓰기가 되는 문제점이 있다.
이는 전에 선언했던 const toDos = [] 를 빈칸으로 비워뒀기 때문에 매번 새로고침 시, 초기화가 진행되어 빈칸으로 시작하는 것이다.
따라서 toDos를 let으로 바꿔 업데이트 가능하게 바꿔주고 toDos에 parsedToDos 값을 넣어준다.
let toDos = [];
if (savedToDos !== null) {
const parsedToDos = JSON.parse(savedToDos);
toDos = parsedToDos;
parsedToDos.forEach(paintToDo);
}
이렇게 말이다.
이렇게 코딩 완료 시, 새로고침에도 기존에 데이터가 남아있어 날라가는 것을 방지할 수 있다.
그러나 아직 문제점은 남아있다.
리스트를 작성하고 ❌ 버튼을 눌러 리스트 삭제 시, 화면에서는 없어지지만
localStorage에는 값이 아직 남아있다.
다음엔 localStorage에서 삭제하는 방법을 공부해보겠다.
const toDoForm = document.getElementById("todo-form");
const toDoInput = toDoForm.querySelector("input");
const toDoList = document.getElementById("todo-list");
function deleteToDo(event) {
const li = event.target.parentElement;
li.remove();
}
const TODOS_KEY = "todos";
let toDos = [];
function saveToDos() {
localStorage.setItem(TODOS_KEY, JSON.stringify(toDos));
}
function paintToDo(newToDo) {
const li = document.createElement("li");
const span = document.createElement("span");
span.innerText = newToDo;
const button = document.createElement("button");
button.innerText = "❌";
button.addEventListener("click", deleteToDo);
li.appendChild(span);
li.appendChild(button);
toDoList.appendChild(li);
}
function handleToDoSubmit(event) {
event.preventDefault();
const newToDo = toDoInput.value;
toDoInput.value = "";
toDos.push(newToDo);
paintToDo(newToDo);
saveToDos();
}
toDoForm.addEventListener("submit", handleToDoSubmit);
const savedToDos = localStorage.getItem(TODOS_KEY);
if (savedToDos !== null) {
const parsedToDos = JSON.parse(savedToDos);
toDos = parsedToDos;
parsedToDos.forEach(paintToDo);
}