본문 바로가기
Front-end/Vanilla JS

Vanilla JS - 지역저장소를 이용해서 toDoList 만들기(2)

by devraphy 2020. 10. 16.

- 이전 포스팅에서 지역저장소에 데이터를 저장하여 toDoList를 생성하는 방법까지 알아보았다. 

- 오늘은 삭제하는 기능을 구현해보자. 

 

1. 삭제기능

- 아래의 사진처럼, 할일을 작성하면 li태그에 입력값이 하나씩 저장되고 x버튼을 눌러 삭제하는 방식이다.

 

- 삭제를 위해 x버튼을 눌렀을 때, HTML상에서 어떤 x버튼이 어떤 li태그에 존재하는지 구분할 수 있어야 한다. 어떻게 할 수 있을까? 

 

a) event.target

- event.target은 해당 event가 발생한 엘리먼트를 가리키는 기능이다. 

- eventHandler 함수를 작성할 때, 매개변수로 선언되어야지 사용할 수 있다. 

 

 

[구현내용]

 

1) x버튼이 눌렸을 때, event.target을 이용하여 어떤 엘리먼트에서 이벤트가 발생했는지 찾은 후, 해당 버튼을 감싸고 있는 부모태그인 li태그를 찾아 삭제한다.

  • event.target을 사용하여 해당 이벤트가 발생한 엘리먼트를 찾는다.
  • parentNode를 사용하여 해당 버튼을 감싸고 있는 부모태그를 찾는다.
  • 찾은 자료를 바탕으로 removeChild()를 사용하여 toDoList에서 해당 엘리먼트를 삭제한다. 

2) 삭제를 마친 후, 지역저장소에 저장되어있는 데이터(toDos)의 id값과 비교하여 HTML상에 존재하지 않는 id값을 찾은 후 filter함수를 이용하여 해당 id값을 제외한 나머지 값만을 다시 지역저장소에 저장한다. 여기서, const로 설정되어 있는 toDos를 반드시 let으로 바꿔주어야 한다. (toDos = cleanToDos; 이 부분 때문)

  • filter()를 사용하여 지역저장소와 toDos의 id값을 비교한 뒤, 양쪽 모두에 없는 값을 제외한 나머지를 리스트로 생성한다.
  • 기존의 지역저장소 데이터를 갖고 있는 리스트(toDos)와 새로운 리스트(filter로 만든 리스트)를 교체한다. 
  • toDos에 새로운 리스트를 적용하기 위하여 toDos 선언시 사용한 const를 let으로 바꿔준다. 

 

[JS코드]

const toDoForm = document.querySelector(".js-toDoForm");
const toDoInput = toDoForm.querySelector("input");
const toDoList = document.querySelector(".js-toDoList");

const TODOS_LS = "toDos";

let toDos = []; // toDo를 저장할 리스트 생성
 
 // 1번 과정
 function deleteToDo(event) { // 매개변수로 event 선언 
    const btn = event.target; // 어떤 버튼인지 찾아낸 후 
    const li = btn.parentNode; // 해당 버튼을 감싸고 있는 부모태그(li태그)를 찾는다. 
    toDoList.removeChild(li); // removeChild(): 특정 엘리먼트의 자식 엘리먼트를 삭제하는 함수 
    // 아직 완벽하게 지워진 것이 아니다. HTML 상에서만 삭제될 뿐 지역저장소에는 기록이 남아있기때문에
    // 새로고침을 하면 지웠던 리스트가 다시 생성된다. 그러므로 로컬 저장소에 있는 정보도 삭제해줘야 한다.
    
 // 2번 과정 
    const cleanToDos = toDos.filter(function filterFn(toDo) { 
        // filterFn(): 삭제 버튼을 작동으로 어떤 li태그가 삭제되었는지 확인하기위한 함수  
        return toDo.id !== parseInt(li.id);
        // id를 비교하여 없는 삭제된 id를 찾아 반환한다
        // parseInt(): 엘리먼트의 속성값은 string이고 toDo.id는 정수형이므로 문자를 정수로 형변환한다. 
    }); 
    // filter(): filterFn()에서 반환된 데이터를 toDos 리스트에서 걸러내는 함수 
    toDos = cleanToDos;
    // 초기에 선언할 때, toDos는 const타입으로 값을 변경할 수 없었다.
    // 그러나 삭제기능 구현을 위해서 let타입으로 변경한다. (toDos = cleanToDos; 이걸 하기 위해서)
    saveToDos();
}

function paintToDo(text) {
  const li = document.createElement("li"); 
  const delBtn = document.createElement("button"); 
  const span = document.createElement("span");
  const newId = toDos.length + 1;

  delBtn.innerText = "❌";
  delBtn.addEventListener("click", deleteToDo); // 삭제버튼에 이벤트 리스너 등록 
  span.innerText = text;

  li.appendChild(span); 
  li.appendChild(delBtn);
  toDoList.appendChild(li);
  li.id = newId; 
  
  const toDoObj = {
    text: text,
    id: newId,
  };
  toDos.push(toDoObj);
  saveToDos(); 
}

 

 

b) 기능 확인

 

- 이처럼 지역저장소와 엘리먼트 데이터의 생성과 삭제가 잘 되는 것을 볼 수 있다.

- 여기까지, 기본적인 toDoList가 완성되었다. 


2. 전체 코드(주석포함)

const toDoForm = document.querySelector(".js-toDoForm");
const toDoInput = toDoForm.querySelector("input");
const toDoList = document.querySelector(".js-toDoList");

const TODOS_LS = "toDos";

let toDos = []; // toDo를 저장할 리스트 생성

function saveToDos() {
  // 저장할 때, object를 string으로 형변환
  // JSON.stringify()를 이용하면 object를 string으로 형변환할 수 있다.
  localStorage.setItem(TODOS_LS, JSON.stringify(toDos));
}

function deleteToDo(event) {
  // 삭제 버튼이 클릭되면 li태그가 지워지는 기능을 구현한다.
  // 하지만, 어떤 버튼이 클릭되었는지 알지 못한다. 이를 해결하기 위해 target을 사용한다.
  // 또 다른 문제점은 해당 삭제버튼이 들어있는 li태그(부모)를 찾아야 한다는 것이다.
  // console.dir(event.target)을 실행하면 해당 이벤트가 실행된 엘리먼트의 속성정보를 볼 수 있다.
  // 속성정보 중 parentNode 라는 속성값을 사용하면 부모 엘리먼트를 찾을 수 있다.
  const btn = event.target;
  const li = btn.parentNode;
  toDoList.removeChild(li); // removeChild(): 특정 엘리먼트의 자식 엘리먼트를 삭제하는 함수
  // 아직 완벽하게 지워진 것이 아니다. HTML 상에서만 삭제될 뿐 지역저장소에는 기록이 남아있기때문에
  // 새로고침을 하면 지웠던 리스트가 다시 생성된다. 그러므로 로컬 저장소에 있는 정보도 삭제해줘야 한다.

  const cleanToDos = toDos.filter(function filterFn(toDo) {
    // filterFn(): 삭제 버튼을 작동으로 어떤 li태그가 삭제되었는지 확인하기위한 함수
    return toDo.id !== parseInt(li.id);
    // id를 비교하여 없는 삭제된 id를 찾아 반환한다
    // parseInt(): 엘리먼트의 속성값은 string이고 toDo.id는 정수형이므로 문자를 정수로 형변환한다.
  });
  // filter(): filterFn()에서 반환된 데이터를 toDos 리스트에서 걸러내는 함수
  toDos = cleanToDos;
  // 초기에 선언할 때, toDos는 const타입으로 값을 변경할 수 없었다.
  // 그러나 삭제기능 구현을 위해서 let타입으로 변경한다. (toDos = cleanToDos; 이걸 하기 위해서)
  saveToDos();
}

function paintToDo(text) {
  const li = document.createElement("li"); // li 생성(엘리먼트를 생성시키는 함수 )
  const delBtn = document.createElement("button"); //버튼 생성
  const span = document.createElement("span");
  const newId = toDos.length + 1;

  delBtn.innerText = "❌";
  delBtn.addEventListener("click", deleteToDo);
  span.innerText = text;

  li.appendChild(span); // li 태그 내부에 자식태그를 생성하는 함수
  li.appendChild(delBtn);
  toDoList.appendChild(li);
  li.id = newId; // li태그를 삭제할 때, 어떤 li태그를 삭제할 것인지 구분하기 위해 id속성값을 부여

  const toDoObj = {
    // toDos 배열안에 넣을 정보를 setup
    text: text,
    id: newId,
  };
  toDos.push(toDoObj); // setup된 정보를 배열에 push
  saveToDos(); // toDos를 지역저장소에 저장하는 함수를 호출
}

function handleSubmit(event) {
  event.preventDefault();
  const currentValue = toDoInput.value;
  paintToDo(currentValue);
  toDoInput.value = "";
}

function loadToDos() {
  const loadedToDos = localStorage.getItem(TODOS_LS);
  if (loadedToDos !== null) {
    const parsedToDos = JSON.parse(loadedToDos);
    // 스트링으로 바뀌어 저장되어있는 value를 불러와서 사용해야 하므로
    // JSON을 활용하여 string을 다시 object로 형변환 시켜준다.
    parsedToDos.forEach(function (toDo) {
      // parsedToDos에 들어있는 각 데이터를 toDo라는 매개변수에 넣어서 아래 명령문을 실행힌다.
      paintToDo(toDo.text);
    });
  }
}

function init() {
  loadToDos();
  toDoForm.addEventListener("submit", handleSubmit);
}

init();

 

 

[HTML 코드]

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vanilla JS</title>
    <link rel="stylesheet" href="index2.css" />
  </head>
  <body>
    <form class="js-toDoForm">
      <input type="text" placeholder="Write what you need to do." />
    </form>
    <ul class="js-toDoList"></ul>
    <script src="todo.js"></script>
  </body>
</html>

 

댓글