이번에 소개할 기능은 list의 내용을 수정하는 기능이다. challenge 도중 다른 사람의 작품을 보다가 수정할 수 있는 기능을 추가한 사람이 있어서 , 그분의 코드를 읽고 이해한 다음 내 나름대로 적용해보았다. 논리의 흐름은 이렇다..
1 to-do list 더블클릭
2 내용이 없어지고 input 박스가 생겨남
3 input 박스에 내용을 입력하고 enter를 누름
4 input.value의 내용과 버튼들이 다시 생기고 localstorage(이하 LS)에 해당 list의 내용이 input.value로 수정됨
5 LS 저장
6 ESC를 누르거나 커서를 input 바깥에서 누르면 기존의 내용이 그대로 복원됨..
그럼 번호순으로 자세히 설명해보겠다..
더블클릭은 eventlistner "dbclick" 으로 구현했고, input박스는 replacechild로 구현했다. 좀더 자세하게 코드로 알아보자.
const handleEdit = (event) => {
const isSpan = event.target.closest("span");
// event.target은 li 안에 있는 태그들을 말한다.closest("span")이라고 하면 그 태그들 중에 span 을 찾거나 가장 가까운걸 찾아서 return 하라는 뜻. 결국 span값만 선택하겠다는 거다.
if (isSpan) {
// 그게 span이라면 아래의 코드를 진행(따라서 span말고 다르걸 클릭하게 되면 이 코드가 실행안되는 거임)
const li = isSpan.parentNode;
const span = li.querySelector("span");
const input = document.createElement("input");
input.value = span.innerText; // input이 생겨나고 기존의 span 내용이 input에 입력된다
li.querySelector(".delBtn").classList.add("non-showing");
if (li.querySelector(".finishBtn") !== null) {
li.querySelector(".finishBtn").classList.add("non-showing");
}
if (li.querySelector(".backBtn") !== null) {
li.querySelector(".backBtn").classList.add("non-showing");
} // 버튼들을 없애는 작업이다.
input.name = span.innerHTML;
input.maxLength = "100";
input.placeholder = "값을 수정해라카이";
input.autocomplete = "off"; // input을 클릭했을때 자동완성되는 기능이다.
li.replaceChild(input, span); // 그렇게 만든 input이 기존 span과 교체된다
input.focus(); // 그리고 커서가 input에 올라간다.
input.addEventListener("keyup", handleKeyUp); // keyup 이벤트는 input에 어떤 키를 입력했을때를 가정해서 함수를 적용하는 것이다. 아래 설명으로 넘어가자.
input.addEventListener("focusout", handleFocusOut);
}
};
function init() {
todoForm.addEventListener("submit", handleFormSubmit);
entireTodolist.addEventListener("dblclick", handleEdit); // 리스트 전체를 범위로 잡고 더블클릭했을때 이벤트가 발생하게 함.
loadState();
restoreState();
}
input에 뭔가 키를 입력했을때 작동되는 함수이다.
const pendingList = document.getElementById("js_pending"),
finishedList = document.getElementById("js_finished");
function updateFinishedTask(id, text) {
finishedTask.map((finishedElement) => {
if (finishedElement.id === id) {
finishedElement.text = text;
}
});
}
function updatePendingTask(id, text) {
pendingTask.map((pendingElement) => {
if (pendingElement.id === id) {
pendingElement.text = text;
}
});
}
function isLiInTodos(li) {
return li.parentNode.matches("#js_pending");
}
function nonShowingRemover(li, btnName) {
li.querySelector(`.${btnName}`).classList.remove("non-showing");
}
function buttonRestorer(li) {
nonShowingRemover(li, "delBtn");
if (li.querySelector(".finishBtn")) {
nonShowingRemover(li, "finishBtn");
} // 보니깐 nonshowingremover도 반복되어서 위에 따로 빼놓았다.
if (li.querySelector(".backBtn")) {
nonShowingRemover(li, "backBtn");
}
}
function handleKeyUp(event) {
if (event.target.value !== "" && event.key === "Enter") {
// 무언가 입력했으면서 enter 키를 누른상태라면 아래의 코드가 실행된다.
event.target.lastKey = event.key;
// 둘다 로그해보면 target.key는 "Enter" 라고 나오는데 lastKey는 undefined라고 나온다. 그럼 undefined라고 되어있는게 문제가 된다는 말인데, lastKey가 도데체 무엇을 의미하는것일까. 근데 또, event를 로그하고 target.lastKey 를 따라 가보면 "Enter" 라고 되어있다. 여기는 왜 Enter라고 되어있으며 로그했을땐 왜 undefined라고 되어있었던 것일까?
// 이게 무슨 역할을 하는지 궁금해서 삭제하고 돌려보니 아래와 같은 경고가 나온다.
// Failed to execute 'replaceChild' on 'Node': The node to be removed is no longer a child of this node. Perhaps it was moved in a 'blur' event handler?
// 교체하려고 하는 node가 더이상 li의 child가 아니라는 말을 한다. 뭐지... 구글링해보니 설명이 복잡해서 더 헷갈린다.. 일단 더 알아봐야겠다.
const li = event.target.parentNode;
const span = document.createElement("span");
span.innerHTML = event.target.value;
li.replaceChild(span, event.target); // input을 span으로 교체함 다시.
if (isLiInTodos(li)) {
// 위에 있는 isLiInTodos 함수를 살펴보자. li의 부모가 #js_pending 이랑 일치하느냐 이다. 그렇다면 아래 함수가, 아니면 그 아래함수가 실행됨.
updatePendingTask(li.id, span.innerHTML);
} else {
updateFinishedTask(li.id, span.innerHTML);
} //update함수를 살펴보면 ul 안에 있는 li 중에 지금 수정하려는 li.id 랑 같으면 그 안에 있는 텍스트를 input.value로 교체하라는 뜻이다.
saveState(); // 그리고 LS를 저장한다.
buttonRestorer(li);
// 그리고 버튼을 살려냄. 이 코드는 반복이 되어서 따로 refactoring을 하였다.
} else if (event.key === "Escape") {
// 그게 아니고 esc를 누르면 아래코드가 실행된다.
// 원래상태 그대로 복원시키는 것이다.
event.target.lastKey = event.key;
const li = event.target.parentNode;
const span = document.createElement("span");
span.innerHTML = event.target.name;
li.replaceChild(span, event.target);
buttonRestorer(li);
}
}
그리고 focusout(커서를 input 밖에서 클릭할때) 일때 실행되는 코드는 esc눌렀을떄 실행되는 코드랑 같다.
input.addEventListener("focusout", handleFocusOut);
const handleFocusOut = (event) => {
if (event.target.lastKey === undefined) {
// 커서가 바깥으로 가면 lastKey 가 undefined 되어버린다. 그럼 원상복귀다.
const li = event.target.parentNode;
const span = document.createElement("span");
span.innerHTML = event.target.name;
li.replaceChild(span, event.target);
buttonRestorer(li);
}
};