본문 바로가기

Study/Web

[JS] 바닐라 JS | #7.0~7.8) To-Do List 만들기

반응형

이번 강의 한 줄 요약은 투두투두뉴투두뚜두뚜두뚜

#7.0 Setup

아래와 같이 코드를 작성하면 form 안에 있는 input 태그의 default 인 submit을 막고, input 받은 value를 받아서 console.log할 수 있다.

헷갈리지 말아야 할 것은! form 태그와 input 태그를 따로 받아와야 한다는 것이다.

form 태그 -> addEventListener로 submit 감지

input 태그 -> .value로 입력된 값 받아옴

<!----HTML--->
<body>
    <form id="todo-form">
      <input type="text" placeholder="Write a To Do and Press Enter" required />
    </form>
    <ul id="todo-list"></ul>
    <script src="js/todo.js"></script>
  </body>
// JS
const toDoForm=document.getElementById("todo-form");
const toDoInput=toDoForm.querySelector("input");
const toDoList=document.getElementById("todo-list");

function handleToDoSubmit(event) {  // js가 발생한 이벤트를 인자 event로 준다.
  event.preventDefault();
  console.log(toDoInput.value);
}


toDoForm.addEventListener("submit",handleToDoSubmit);

엔터 치면 input 비우기

  toDoInput.value="";

비우기 전에 값을 저장하자. toDoInput의 value를 newTodo라는 새로운 변수에 저장하자.

function handleToDoSubmit(event) {  // js가 발생한 이벤트를 인자 event로 준다.
  event.preventDefault();
  const newTodo = toDoInput.value;  // input의 현재 value를 새로운 변수에 복사
  console.log(toDoInput.value);
  toDoInput.value="";
  console.log(newTodo,toDoInput.value);
}

 

#7.1 Adding ToDos

handleToDoSubmit의 마지막에 paintToDo 함수를 호출하면서 newTodo를 인자로 전달한다.

요로케!

// JS
function paintToDO(newTodo) {
}

function handleToDoSubmit(event) {
	event.preventDefault);
    const newTodo = toDoInput.value;
    toDoInput.value="";
    paintToDO(newTodo);
 }

그리고 todo list를 어떻게 만들 거냐면 js로 html에 element를 추가할 것이다. 요로케!

// JS
  const li = document.createElement("li");
  const span=document.createElement("span");
  li.appendChild(span);
  span.innerText=newTodo;

만들고 싶은 html의 구조는 

<ul id="todo-list">
| <li>
|---- <span> newTodo
|<li>
......

이다. 이걸 만들면 요로케 된다.

 

위 코드에서 개선해야 할 점 두 가지가 뭐냐믄! 

1. list 삭제가 안 된다. -> btn 만들어야 함

2. 새로고침하면 list들이 날아감

 

#7.2 Deleting To Dos

자, 위에서 말한 첫 번째 문제점을 개선해보자. 바로 X 버튼 만들기! 

이렇게 작성해주자. 아래의 코드를 추가한다.

// JS
const button = document.createElement("button");
button.innerText="❌"
li.appendChild(button);

그럼 function paintToDo 함수는 이렇게 된다.

// JS
function paintToDo (newTodo) { // todo 그리는
  const li = document.createElement("li");
  const span=document.createElement("span");
  span.innerText=newTodo;
  const button = document.createElement("button");
  button.innerText="❌"
  li.appendChild(span);
  li.appendChild(button);
  toDoList.appendChild(li);
}

그러면 요론 구조가 된다.

<ul id="todo-list">
| <li>
|---- <span> newTodo
|---- <button> // 삭제 버튼
|<li>
......

잊지 말아야 할 것은 append는 맨 마지막에 놓아야한다는 것이다. 또한 그 순서도 잘 정렬해야 한다.

 

자, 이제 버튼에 click eventListener를 부여해보자. 물론 addeventListener를 사용하는 것은 똑같을 것이다.

하지만 문제가 있다. 바로 여러 개의 버튼이 생성되고, eventlistener는 어떤 버튼이 클릭되는지 모른다는 것이다.

이때 event의 property 중 하나인 target을 살펴보면 된다. 

event.target

어떤 버튼에서 이벤트가 발생했는지를 element 구조로 알려준다. event.target은 클릭된 HTML element다.

// JS
function deleteToDo(event) {
	console.dir(event.target);
}

를 통해 살펴보자. 여기서 parentelement 혹은 parentNode에 주목하면 답이 있다. parentElement는 클릭된 element(즉, event.target)의 부모 element다.

// JS
function deleteToDo(event) {
  console.dir(event.target.parentElement.innerText);
}

라고 치면 뭘 입력했는지 console에서 확인할 수 있다.

우리가 원하는 것을 하기 위해서는 아래와 같은 함수를 만들면 된다!

// JS
function deleteToDo(event) {
  const li =event.target.parentElement;
  li.remove();
}

뽀인트는 const li = event.target.parentElement다. 쩐당~~

 

따라서 지금까지 한 것을 종합하면 아래와 같은 게 완성된다.

 

#7.3 Saving To Dos

아까 말했던 두 가지 문제점 중 하나는 위에서 해결했다. 그럼 이제 새로고침하면 todoList가 날라가는 문제를 해결해보자.

브라우저에 저장하는 것의 KEY는 local storage다. local storage는 새로고침해도 날라가지 않기 때문이다.

 

로직은 이렇다. 

1. 입력한 것을 local storage에 저장한다.

2. 새로고침 하면 local storage에서 저장된 것을 다시 불러온다.

 

자, 우선 local storage에 저장해보자.

local storage에 저장하기

array를 만들고, newTodo가 추가될 때마다 그 텍스트를 array에 push한다.

newTodo를 paint하기 전에 toDos array를 가져와서 newTodo를 push한다.

// JS
const toDos = []; // array 

function handleToDoSubmit(event) {  // js가 발생한 이벤트를 인자 event로 준다.
  event.preventDefault();
  const newTodo = toDoInput.value;  // input의 현재 value를 새로운 변수에 복사
  toDoInput.value="";
  toDos.push(newTodo); // paintToDo 전에 toDos 배열에 newTodo 값 저장함.
  paintToDo(newTodo);
}

이제 toDos를 localStorage에 넣어야 한다. 하지만 localStorage에는 array를 저장할 수 없고, only text만 저장할 수 있다. (-> 배열을 값으로 넣어도 error가 뜨지는 않지만 자동으로 배열 안의 내용물을 빼낸 후 배열의 형태를 파괴한 후 저장한다. )

 

조금씩 헷갈리는데 이쯤에서 우리의 로직을 되짚어 보자.
1. 사용자가 form을 submit
2. input 비우고
3. 그 텍스트(newTodo)를 toDos 배열에 push
4. 화면에 toDo 그려줌 (paintToDo 함수)
5. saveToDos 함수에서 toDos local storage에 저장.

오키오키. 5번까지 구현했다.

 

새로고침해도 local storage에는 newTodos가 array로 남아있다. 하지만 두 가지 문제가 보인다.

1. 새로고침 후 다른 input을 넣으면 todos가 다 사라지고 새롭게 input된 것만 저장되는 문제를 발견할 수 있다.

2. 또한 LS에 저장해둔 todos들을 화면에 보여주지 않는다.

 

두 번째 문제부터 해결하자.

그러기 위해서는 LS 값을 배열로 바꿔줄 필요가 있다.

 

JSON.stringify()

js object나 array 또는 어떤 js 코드건 간에 모두 string으로 변환해준다.

이 코드를 사용하면 아래와 같이 LS에 저장된다.

// JS
function saveToDos() {
  localStorage.setItem("todos",toDos);
}

아래 코드로 수정해준다. 그러면 비록 "문자열"이지만 배열의 "형태"로 저장되는 것을 볼 수 있다.

// JS
function saveToDos() {
  localStorage.setItem("todos",JSON.stringify(toDos));
}

 

그러면 이렇게 LS에 저장해둔 것을 어떻게 불러올까?

#7.4 Loading To Dos part One

JSON.parse()

string을 array로 만든다.

아래의 코드를 추가하자.

// JS
const savedToDos = localStorage.getItem(TODOS_KEY);

if(savedToDos !== null) { // 만약 savedToDos가 localStorage에 존재하면 -> 그냥 if(savedToDos) {} 이렇게 해도 됨
  const parsedToDos = JSON.parse(savedToDos);
  parsedToDos.forEach
}

 

forEach()

forEach는 array에 있는 각각의 item에 대해 function을 실행할 수 있게 한다.

예를 들어 이해해보자.

// JS
function sayHello(item) {
  console.log("this is the turn of",item);
}

array.forEach(sayHello);

이렇게 코드를 작성하면, array 배열의 각각의 원소마다 sayHello 함수를 실행하라는 것이다.

 

이런 코드를 입력하면

이렇게 된다.

 

그러나 forEach 함수를 사용할 때 주로 arrow function을 사용한다. 위의 방법의 shortcut으로 생각하면 편하고, function을 많이 만들지 않아도 된다.

이렇게 사용한다.

 

그러면 아래와 같이 완성된다. 

 

이 코드의 문제는 a, b, c를 입력한 후 새로고침 후 d,e,f를 입력하면,

화면에는 여전히 a,b,c가 남아있지만 LS에는 d,e,f만 남는다는 것이다. 즉 예전 것을 덮어쓴다.

새로운 값과 기존 값을 모두 유지하도록 수정해야 한다.

이렇게 된 이유는 application이 시작될 때 toDos array는 항상 비어있기 때문이다. 바로 아래의 코드!

// JS
const toDos = [];

그리고 newToDo를 작성하고 form을 submit할 때마다 newToDo를 toDos array(빈 array)에 그냥 push하게 된다.

//JS
function handleToDoSubmit(event) {
	event.preventDefault();
    const newTodo = toDoInput.value;
    toDoInput.value="";
    toDos.push(newTodo);
    paintToDo(newTodo);
    saveToDos();
}

그리고 그 toDo를 저장할 때, 새로운 toDo들만 포함하고 있는 array를 저장하는 것이다.

// JS
function saveToDos() {
	localStorage.setItem(TODOS_KEY, JSON.stringify(toDos));
}

 

이를 개선해보자!

1. const로 선언된 toDos 배열을 let으로 바꿔서 업데이트 가능하게 한다.

let toDos = [];

2. LS에 값(toDo)이 있을 때를 else 구문으로 구분하자. LS에 값이 있으면 toDos에 parsedToDos를 넣어서 전에 있던 toDo들을 복원한다.

우리가 toDos array를 시작할 때 LS에 저장되어 있는 이전의 toDo들을 불러온다.

if(savedToDos !== null) { // 만약 savedToDos가 localStorage에 존재하면 -> 그냥 if(savedToDos) {} 이렇게 해도 됨
  const parsedToDos = JSON.parse(savedToDos);
  toDos = parsedToDos;
  parsedToDos.forEach(paintToDo)
}

 

그러나 또 문제가 있다. 바로 X를 눌러서 삭제를 해도 새로고침하면 다시 살아돌아 온다는 것이다.

 

#7.6 Deleting To Dos part One

위 문제를 해결하기 위해서는 아래의 코드를 살펴봐야 한다.

function deleteToDo(event) {
  const li =event.target.parentElement;
  li.remove();
}

이는 HTML에서 삭제를 해주지만 어떤 todo text를 데이터베이스(toDos)에서 지워야하는 지는 명령하지 않는다.

이를 위해 우리가 적은 텍스트를 고유하게 만들어 줘야 한다. 우리가 원하는 것은 아래와 같다.

[{id: random, text: "what-to-do"}]

코드를 아래와 같이 사용한다.

unction handleToDoSubmit(event) {  // js가 발생한 이벤트를 인자 event로 준다.
  event.preventDefault();
  const newTodo = toDoInput.value;  // input의 현재 value를 새로운 변수에 복사
  toDoInput.value="";
  const newTodoObj = {
    text: newTodo,
    id: Date.now(),
  }
  toDos.push(newTodoObj);
  paintToDo(newTodo);
  saveToDos();
}

따다~~ 이렇게 id가 저장되어따다다다ㅏ다.

 

고럼 이제 id를 얻어와서 삭제해보좡.

 

#7.7 Deleting To Dos part Two

지우고 싶은 item을 제외하고 새 array를 만든다. filter함수를 사용한다.

filter는 false면 제외하고, true면 그대로 포함한다.

 

#7.8 Deleting To Dos part Three

parseInt 주의!

완성된 코드는 아래와 같다.

 

반응형