얽히고 섥힌 로직
이번에 챌린지 하면서 풀었던 과제가 있다. PENDING(할일) 과 FINISHED(한일)을 구분하고 할일이 끝나면 한일로 이동하도록하고 또 한일에서 할일로 이동하도록 하며 새로고침해도 그대로 이어지도록 만들었다. 사실 내가 그렇게 되도록 코드를 짜봤는데 너무 복잡해서 미완성으로 남았었다. 그러다 , 다음날 니코가 올려준 코드를 보고 진짜 대단하다 싶어서 하나하나 로직의 연결고리, 문법을 다 이해하였고 내 TODO LIST에 적용했다.
로직의 순서를 살펴보자
1.입력을 하면 입력한 값이 pending에 뿌려진다.
2.그리고 LS(localstorage)에 저장된다.
3.삭제, 끝냄 버튼을 만들고 그에 따른 함수를 추가한다.
4.삭제버튼을 누르면 없어지고, 끝냄 버튼을 누르면 finished로 이동하면서 그와 관련된 LS정보또한 옮겨진다.
5.finised에서 pending으로 옮겨질때도 마찬가지다.
6.그리고 새로고침하면 LS값이 parsed 된다음 paint 함수에 의해 각자 알맞게 나타난다.
그럼 1번부터 먼저 살펴보자
1. pending에 뿌리기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const pendingList = document.getElementById("js-pending"),
finishedList = document.getElementById("js-finished"),
form = document.getElementById("js-form"),
input = form.querySelector("input"); //먼저 DOM 변수를 설정해준다.
const PENDING = "pedning";
const FINISHED = "finished";
// LS에 저장될 KEY값을 설정함
let pendingTasks, finishedTasks;
//LS 의 VALUE 로 저장될 LIST 값인데 여기서 명확하게 명시안해주는 이유는 load함수 설명할때 알게된다.
function init() {
form.addEventListener("submit", handleFormSubmit);
loadState();
restoreState();
}
init();
//언제나 그렇듯이 init함수를 만들고 안에 몇가지 함수를 추가한다.
먼저 handleFormSubmit 함수에 대해 알아보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function getTaskObject(text) {
return {
id: String(Date.now()), // 기발한 아이디어
text, // 이렇게 설정해도 나중에 저장될떄는 text:text이런식으로 저장됨.
};
}
function savePendingTask(task) {
pendingTasks.push(task);
}
function handleFormSubmit(e) {
e.preventDefault();
const taskObj = getTaskObject(input.value); //getTask 함수는 여기서 딱 한 번 쓰이기 때문에 따로 빼지 않아도 상관없는데 니코는 강조하기 위해 따로 빼둔것 같다
input.value = "";
paintPendingTask(taskObj);
savePendingTask(taskObj);
saveState();
} // 그다음 pending에 뿌려주고 pendinglist에 값을 입력한다음 LS를 저장한다.
그럼 paintPendingTask에 대해 알아보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
function removeFromPending(taskId) {
pendingTasks = pendingTasks.filter(function (task) {
return task.id !== taskId;
});
}
function removeFromFinished(taskId) {
finishedTasks = finishedTasks.filter(function (task) {
return task.id !== taskId;
});
}
// tasklist 중에 li.id가 아닌것만 찾아서 다시 list에 넣으라는 뜻
function deleteTask(e) {
const li = e.target.parentNode;
li.parentNode.removeChild(li);
removeFromFinished(li.id);
removeFromPending(li.id);
saveState();
}
function buildGenericLi(task) {
const li = document.createElement("li");
const span = document.createElement("span");
const deleteBtn = document.createElement("button");
span.innerText = task.text;
deleteBtn.innerText = "❌";
deleteBtn.addEventListener("click", deleteTask);
// li, span, 삭제 button을 만든다. 삭제버튼에는 함수를 추가하여 기능을 입힌다.
li.append(span, deleteBtn);
// 그리고 li에 span과 삭제버튼을 child로 입력함
li.id = task.id; // li id 도 설정해줌
return li; // 나중에 ul의 child로 넣어주기 위해 li을 return한다.
}
function paintPendingTask(task) {
const genericLi = buildGenericLi(task);
// buildGeneric 함수가 반복되기 때문에 refactoring을 해준것이다.
const completeBtn = document.createElement("button");
completeBtn.innerText = "✅";
//그리고 완료버튼에 기능을 추가한다.
completeBtn.addEventListener("click", handleFinishClick);
genericLi.append(completeBtn);
pendingList.appendChild(genericLi);
//그러고 ul에 genericli 를 추가함.
}
완료버튼에 대해 알아보자.
2. 완료버튼 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function findInPending(taskId) {
return pendingTasks.find(function (task) {
return task.id === taskId;
});
}
function addToFinished(task) {
finishedTasks.push(task);
}
function handleFinishClick(e) {
const li = e.target.parentNode;
li.parentNode.removeChild(li);
// 일단 버튼 클릭된 li는 pending 화면에서 지우고
const task = findInPending(li.id);
// 해당 list중에서 해당 li를 찾고 return해서 task에 할당함
removeFromPending(li.id); //pendinglist에서 지우고
addToFinished(task); // finishedlist 에 입력함
paintFinishedTask(task); // 그리고 해당 li를 다시 finished 화면에 입력함
saveState();
}
//P.S: find() 와 filter()의 차이. 조건과 일치한 결과를 딱 찾으면 바로 끝내느냐 아님 조건과 일치한 결과를 모두 찾아내느냐의 차이다. 따라서 find()는 결과값이 1개이고 filter()는 결과값이 2개이상일 수 있다.
어디까지 했더라…아 init()함수에서 submit과 연결된 함수를 설명했었지. 그럼 이제 새로고침했을때도 나타나도록 load와 restore 함수에 대해서 알아보자.(로직이 복잡하다.)
3. load와 restore 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
function loadState() {
pendingTasks = JSON.parse(localStorage.getItem(PENDING)) || [];
finishedTasks = JSON.parse(localStorage.getItem(FINISHED)) || [];
} // Tasks는 LS에 저장된 PENDING인데 없을 경우 빈 list(array)를 할당한다. 조건부 할당이기 때문에 ("||" 때문에) 애초에 명시안해준것이다.
function restoreState() {
pendingTasks.forEach(function (task) {
paintPendingTask(task);
});
finishedTasks.forEach(function (task) {
paintFinishedTask(task);
});
} // load에서 Tasks가 할당되었기 때문에 Tasks안에 있는 값들을 forEach 함수를 돌려 하나하나 손수 화면에 입력한다.
paintFinished 함수에 대해 알아보자.
1
2
3
4
5
6
7
8
function paintFinishedTask(task) {
const genericLi = buildGenericLi(task);
const backBtn = document.createElement("button");
backBtn.innerText = "⏪";
backBtn.addEventListener("click", handleBackClick);
genericLi.append(backBtn);
finishedList.append(genericLi);
} // pending과 동일한 로직이다. span,li,button(기능추가) 한다음 ul 의 child로 추가한다.
반응형으로 만들어서 모바일에서도 사용가능하게 했다.
1
2
3
4
5
6
@media screen and (max-width: 600px) {
.list {
grid-template-columns: repeat(1, 1fr);
gap: 0;
}
}
P.S: 로직이 복잡해서 어떻게 하면 쉽게 이해할 수 있을까 연구하다가 그림을 그리고 그걸 눈앞에서 배열하면 더 확실하게 다가오지 않을까 하는 마음에서 종이를 자르고 함수를 그려 배열해보았다. 좀더 쉽게 와닿았느냐? 딱히 그런것 같지도 않다 ㅋㅋ