To Do 삭제하기
마지막 To Do 삭제 기능이다.
삭제를 위해서는 어떤 것이 필요할까?
- 삭제를 위한 버튼이 필요하다.
- 삭제 버튼을 클릭하면, 해당하는 to Do가 삭제되어야 한다.
이전 2편에서 완성한 코드를 가지고 시작해보자.
이전 포스팅 JS 코드
const toDoForm = document.querySelector(".toDoForm");
const toDoInput = toDoForm.querySelector("input");
const toDos = document.querySelector(".toDos");
const TODOLIST = "toDoList";
let toDoList = [];
function saveToDo(toDo) {
const toDoObj = {
text: toDo,
id: toDoList.length + 1,
};
toDoList.push(toDoObj);
localStorage.setItem(TODOLIST, JSON.stringify(toDoList));
}
function paintToDo(toDo) {
const li = document.createElement("li");
const span = document.createElement("span");
span.innerHTML = toDo;
li.appendChild(span);
toDos.appendChild(li);
}
function createToDo(event) {
event.preventDefault();
const toDo = toDoInput.value;
paintToDo(toDo);
saveToDo(toDo);
toDoInput.value = "";
}
function loadToDoList() {
const loadedToDoList = localStorage.getItem(TODOLIST);
if (loadedToDoList !== null) {
const parsedToDoList = JSON.parse(loadedToDoList);
for (let toDo of parsedToDoList) {
const { text } = toDo;
paintToDo(text);
saveToDo(text);
}
}
}
function init() {
loadToDoList();
toDoForm.addEventListener("submit", createToDo);
}
init();
삭제 버튼을 추가하고 삭제하기
버튼은 기존 paintToDo 함수에서 추가해야 한다.
function paintToDo(toDo) {
const li = document.createElement("li");
const span = document.createElement("span");
const delButton = document.createElement("button"); // 추가
delButton.innerText = "Del"; // 추가
delButton.addEventListener("click", delToDo); // 추가
span.innerHTML = toDo;
li.appendChild(span);
li.appendChild(delButton); // 추가
li.id = toDoList.length + 1; // 추가
toDos.appendChild(li);
}
삭제 버튼을 만들고, list 태그안에 toDo가 담긴 span과 함께 버튼도 넣어준다.
삭제 버튼에는 클릭을 감지하는 이벤트 리스너를 추가한다.
삭제 버튼을 클릭하면 해당하는 toDo와 삭제 버튼을 모두 지워야 하므로, list 태그 자체를 지우면 된다.
li 태그에 id 값을 부여해주면, 기존 데이터에 데이터마다 동일한 id값을 가지고 있으므로 삭제에 활용할 수 있다.
삭제 버튼을 클릭했을 때 발동하는 delToDo 함수를 만들어보자.
function delToDo(event){
console.log(event);
}
eventListener 메서드에 콜백으로서 사용된 함수는 기본적으로 event라는 인자를 받는다.
(인자 이름을 event라고 명명하지 않고, 명명하고 싶은 변수 asdf라고 해도 된다.)
이 인자를 console.log
를 통해 확인해보면, 아래와 같다.
당장 눈에 띄는 속성으로는 offset,page,screenX,Y 등과 target, type, x,y값 등이 있다.
이를 이용해서 마우스 포인터 위치에 따라서 어떤 동작을 수행하는 재밌는 작업도 가능할 듯 하다.
아무튼, 우리는 target에 관심을 두자.
target속성이 주는 button을 이용해서 그 버튼이 속한 li를 가져올 것.
function delToDo(event) {
const { target: button } = event;
console.log(button);
}
2편에서도 말했듯이,const button = event.target ;
와 const {target:button} = event;
는 같은 기능이다.
궁금하다면, 구조분해할당 을 보면 되겠다.
이렇게 가져온 event.target을 출력해보면 다음과 같다.
이제 클릭한 button 태그가 속하는 li태그를 가져와보자.
function delToDo(event) {
const { target: button } = event;
const li = button.parentNode;
console.log(li);
}
parentNode메서드는 해당 HTML 태그의 부모 태그를 반환한다.
첫번째부터 마지막까지 삭제 버튼을 차례대로 클릭해보자.
우리가 클릭한 삭제 버튼에 대응하는 li 태그를 보여주는데, 앞서 설정해 둔 id 태그도 함께 보인다.
이제 이 li 태그를 이용하면 삭제를 할 수 있을 것 같다.
function delToDo(event) {
const { target: button } = event;
const li = button.parentNode;
toDos.removeChild(li);
}
우리가 1편에서 appendChild 을 이용해서 태그에 자식 태그를 넣었던 것처럼,
removeChild 를 이용해서 태그에서 자식 태그를 지울 수도 있다.
사실 이 removeChild 메서드는 해당 태그를 지우고 끝나는 것 처럼 보이지만,
부모 노드와 지우려는 자식 노드의 부모-자식관계를 끊어 DOM 트리에서 해제하는 것이다.
그래서 최종적으로는 관계를 끊은 해당 노드의 참조를 반환하는데,
이것을 이용하면 finishedToDos.appendChild(toDos.removeChild(li));
와 같이
완료 목록 finishedToDos을 만들어서, 옮길 수도 있다.
아무튼, 이제 우리는 "끝낸 할 일"을 삭제할 수 있다.
1편부터 여기까지 읽어왔다면, 당연히 이게 완전한 삭제를 의미하진 않을 것이라고 생각할 것이다.
맞다. 우리는 그냥 화면에서 태그를 지운 것이지 실제 데이터를 지우진 않았다.
우리가 만든 페이지를 새로고침하면, 삭제하기 전 상태로 복구된다.
태그를 없애는 것과 더불어, 우리는 local storage에 저장된 데이터도 지워야 한다.
local storage에서 데이터 삭제하기
localStorage mdn에 따르면, removeItem 메서드를 이용하면 저장된 데이터를 지울 수 있다.
그런데, 우린 기존에 데이터를 "toDoList" : "[{todo 1 ...}, {todo 2 ...}, {todo 3 ...}, ...]"
형태로 저장했다.
removeItem을 이용하면, key(toDoList)에 해당하는 값을 지우므로, 모든 데이터가 삭제될 것이다.
그러므로 다른 방법을 찾아야 한다.
이 데이터는, 우리 JavaScript 파일에서 toDos 배열이다.
먼저 우리는 toDos 배열에서 우리가 클릭한(삭제할) toDo의 아이디와 일치하는 toDo 객체를 지우고
그 toDos 배열을 local storage에 저장해주면 된다.
function delToDo(event) {
const { target: button } = event;
const li = button.parentNode;
toDos.removeChild(li);
toDoList = toDoList.filter((toDo) => toDo.id !== li.id); // 추가
console.log(toDoList); // 추가
}
filter 함수를 이용하면, 배열의 각 요소가 작성한 조건을 만족할 때(조건의 결과가 true 일 때) 그 요소를 리턴해준다.
filter는 배열을 리턴하는데 이 배열의 요소에는 조건을 만족하지 못한 요소는 제외된다.
toDoList = toDoList.filter((toDo) => toDo.id !== li.id);
에서 filter() 안에 들어간 이상한 모양에 대해 낯설 수도 있는데, 이 것은 화살 함수를 참고하자.
(toDo) => toDo.id !== li.id
형태는,function (toDo) { return toDo.id !== li.id;}
와 같다.
여기서 toDo는 배열의 각 요소를 의미한다.
그래서 이제 삭제 버튼을 눌러보면, 해당하는 id가 삭제된 새로운 배열이 콘솔에 찍힐 것이다.
그런데 예상과는 다르게 배열이 변하지 않는다.
아마 2가지로 추측할 수 있을건데,
- toDoList가 const로 선언되었다. => 이 문제였으면, "toDoList가 const로 선언되었다!" 라고 먼저 경고가 날라왔을 것
- filter함수 내에서 사용된 toDo.id와 li.id의 데이터 타입이 다르다. => 이 문제를 확인해보자.
다음과 같이 toDo.id의 데이터 타입은 number지만, li.id의 데이터 타입은 string이기 때문에 발생한 문제다.
function delToDo(event) {
const { target: button } = event;
const li = button.parentNode;
toDos.removeChild(li);
toDoList = toDoList.filter((toDo) => toDo.id !== Number(li.id)); // 변경
console.log(toDoList);
}
li.id 타입을 숫자로 바꿔서 다시 삭제 버튼을 클릭하면,
삭제 버튼을 클릭할 때 마다 배열의 원소 갯수가 줄어든다.
이제 마지막으로, 삭제 버튼을 누를 때 마다, 바뀐 배열로 local storage의 데이터를 갱신하면 된다.
function delToDo(event) {
const { target: button } = event;
const li = button.parentNode;
toDos.removeChild(li);
toDoList = toDoList.filter((toDo) => toDo.id !== Number(li.id));
localStorage.setItem(TODOLIST, JSON.stringify(toDoList)); // 추가
}
이제 local storage에서도 toDo가 삭제되었기 때문에 새로고침을 해도 삭제된 상태가 유지된다.
마지막으로 중복되는 저장 코드 분리하기
function saveToDo(toDo) {
const toDoObj = {
text: toDo,
id: toDoList.length + 1,
};
toDoList.push(toDoObj);
localStorage.setItem(TODOLIST, JSON.stringify(toDoList)); // 중복
}
function delToDo(event) {
const { target: button } = event;
const li = button.parentNode;
toDos.removeChild(li);
toDoList = toDoList.filter((toDo) => toDo.id !== Number(li.id));
localStorage.setItem(TODOLIST, JSON.stringify(toDoList)); // 중복
}
// setItem ~ 다음의 코드가 중복되므로, saveToDoList 라는 저장전용함수를 하나 만들어두자.
function saveToDoList() {
localStorage.setItem(TODOLIST, JSON.stringify(toDoList));
}
function saveToDo(toDo) {
const toDoObj = {
text: toDo,
id: toDoList.length + 1,
};
toDoList.push(toDoObj);
saveToDoList();
}
function delToDo(event) {
const { target: button } = event;
const li = button.parentNode;
toDos.removeChild(li);
toDoList = toDoList.filter((toDo) => toDo.id !== Number(li.id));
saveToDoList();
}
전체 코드
const toDoForm = document.querySelector(".toDoForm");
const toDoInput = toDoForm.querySelector("input");
const toDos = document.querySelector(".toDos");
const TODOLIST = "toDoList";
let toDoList = [];
function saveToDoList() {
localStorage.setItem(TODOLIST, JSON.stringify(toDoList));
}
function saveToDo(toDo) {
const toDoObj = {
text: toDo,
id: toDoList.length + 1,
};
toDoList.push(toDoObj);
saveToDoList();
}
function delToDo(event) {
const { target: button } = event;
const li = button.parentNode;
toDos.removeChild(li);
toDoList = toDoList.filter((toDo) => toDo.id !== Number(li.id));
saveToDoList();
}
function paintToDo(toDo) {
const li = document.createElement("li");
const span = document.createElement("span");
const delButton = document.createElement("button");
delButton.innerText = "Del";
delButton.addEventListener("click", delToDo);
span.innerHTML = toDo;
li.appendChild(span);
li.appendChild(delButton);
li.id = toDoList.length + 1;
toDos.appendChild(li);
}
function createToDo(event) {
event.preventDefault();
const toDo = toDoInput.value;
paintToDo(toDo);
saveToDo(toDo);
toDoInput.value = "";
}
function loadToDoList() {
const loadedToDoList = localStorage.getItem(TODOLIST);
if (loadedToDoList !== null) {
const parsedToDoList = JSON.parse(loadedToDoList);
for (let toDo of parsedToDoList) {
const { text } = toDo;
paintToDo(text);
saveToDo(text);
}
}
}
function init() {
loadToDoList();
toDoForm.addEventListener("submit", createToDo);
}
init();
To Do List를 생성하고 삭제하는 것을 local storage와 여러 DOM 함수들을 이용해서 수행하였다.
정말 못생겼지만, 그래도 쓸만한 페이지를 하나 만든 듯 하다.
CSS 요소를 추가해서 못생긴 입력박스나 버튼의 생김새 등등 좀 더 볼만하게 꾸며보자.
추가로 위에서 언급했던 완료 목록을 만들어서 추가로 관리할 수도 있고, 사용자의 이름을 추가시키거나,
오늘 마친 일 목록을 가지고 하나의 파일로 저장해서 하루하루의 뿌듯함을 모아둘 수도 있겠다.
간단한 후기
상세하게 설명하고자 100줄도 안되는 코드를 설명하는 데 글이 너무 길어진 듯 하다.
다음에는 좀 더 간결하게 작성하고, tmi에 가까운 상세한 설명은 줄이되
코드 자체로서 알아 볼 수 있게 글과 코드를 작성해보도록 노력해야겠다는 생각이 든다.
'JavaScript > vanilla' 카테고리의 다른 글
[vanillaJS] 채팅창 컴포넌트의 스크롤을 다뤄보자. (0) | 2022.08.30 |
---|---|
[VanillaJS] 바닐라JS만으로 To Do List 만들어보자 2 (0) | 2021.04.30 |
[VanillaJS] 바닐라JS만으로 To Do List 만들어보자 1 (0) | 2021.04.30 |