DOM을 이용해서 To Do List 만들기
자바스크립트와 DOM을 이용해서 레이아웃이나 다른 스타일의 추가 없이 간단하게 To Do List를 만들어보자.
먼저 다음과 같이 index.html 파일을 만들어주고, style.css 파일과 script.js 파일을 준비해주자.
아무런 기능이 없는 이 페이지에 다음과 같은 기능을 추가시킬 것이다.
1. 리스트 추가시키기 : 할 일을 입력하고 마우스로 입력 버튼을 클릭하거나 키보드의 엔터키를 입력하면 작성한 내용을 리스트의 마지막에 추가시키기.
2. 리스트 삭제하기 : 각 리스트 옆에 삭제 버튼을 만들어서 클릭하면 해당 리스트가 삭제되게 만들기.
3. 취소선 만들기 : 리스트를 마우스로 클릭하면 리스트의 내용에 취소선을 만들기.
1. 리스트 추가시키기
가장 먼저, HTML DOM을 이용해서 자바스크립트에 button을 제어할 수 있도록 다음과 같은 코드를 추가해주자. index 파일에서 <button> 태그의 id를 enter로 설정해놓았기 때문에, id를 통해 HTML의 요소에 접근하여 변수 button에 정보를 저장해주었다.
var button = document.getElementById("enter");
HTML의 <button> "입력"이 자바스크립트의 button에 제대로 연결됐는지 안 됐는지, 이벤트리스너를 추가해서 간단하게 확인해볼 수 있다.
button.addEventListener("click", function() {
console.log("버튼이 클릭 됐습니다.");
})
a. addEventListener : 이벤트를 등록시킬 수 있는 함수이다.
b. "click" : 이벤트의 유형 중 하나이다. click은 마우스로 클릭했을 경우에 실행되는 이벤트이다. developer.mozilla.org/ko/docs/Web/Events 해당 페이지에서 더 많은 이벤트 레퍼런스를 찾아볼 수 있다.
c. function() : button이 클릭됐을 때, 해당 함수가 작동하게 된다. 테스트를 위해 "버튼이 클릭 됐습니다."라는 내용을 출력해보았다.
입력 버튼을 클릭하니 그림처럼 "버튼이 클랙 됐습니다."라는 텍스트가 출력되는 것을 확인할 수 있다. 이로서 자바스크립트의 button에 HTML의 <button>인 "입력"을 연결시키는 방법을 알게 되었다.
입력 버튼이 리스트에 추가시키는 기능을 갖기 위해서는 input에 입력된 데이터를 받아와서, 그 데이터를 <ul>의 자식으로 추가시키는 방법을 사용하면 될 것이다.
이를 위해 index 파일에서 <input> 태그의 id를 input으로 추가해주고, 다시 자바스크립트로 돌아와 <input> 태그의 값을 저장하기 위한 변수 inputData를 선언하고 DOM을 이용해서 연결시켜 주자. <ul> 역시 마찬가지로 연결해주면 된다.
var button = document.getElementById("enter");
var inputData = document.getElementById("input");
var ul = document.querySelector("ul");
그러고 나서 button에 이벤트리스너를 이용해서 리스트를 추가시키는 동작을 추가하면 된다. createElement는 괄호 안에 입력한 요소를 생성할 수 있는 함수이다. 이를 이용해서 <ul> 태그에 추가할 <li>를 생성할 수 있고, 생성된 li에 createTextNode를 이용해서 요소의 최말단에 inputData의 값을 추가해주고, li를 ul의 자식으로 추가하여 리스트에 추가하는 동작을 구현했다. 마지막에 inputData의 값을 공백으로 초기화시켜주어 입력 버튼을 클릭해도 해야 할 일이 남아있는 현상을 방지하였다.
button.addEventListener("click", function() {
var li = document.createElement("li");
li.appendChild(document.createTextNode(inputData.value));
ul.appendChild(li);
inputData.value = "";
})
그런데 공백인 상태에서 입력버튼을 클릭해도 리스트가 추가되는 현상이 발생한다. 이를 해결하기 위해 inputData의 value 길이가 0보다 클 때에만 함수가 작동할 수 있도록 조건을 추가해주자.
button.addEventListener("click", function () {
if (inputData.value.length > 0) {
var li = document.createElement("li");
li.appendChild(document.createTextNode(inputData.value));
ul.appendChild(li);
inputData.value = "";
}
})
버튼이 클릭됐을 때, 리스트에 추가되는 이벤트가 발생한 것처럼 inputData에서 키보드의 엔터키를 입력했을 때, 리스트에 추가되는 이벤트를 발생시킬 수 있다.
button과 inputData에 이벤트리스너를 추가시켜 이벤트를 등록시키는 방식은 같지만, 키보드로 데이터를 입력할 경우 조금 다른 방법을 써야 한다.
inputData.addEventListener("keypress", function (event) {
if (inputData.value.length > 0 && event.keyCode === 13) {
var li = document.createElement("li");
li.appendChild(document.createTextNode(inputData.value));
ul.appendChild(li);
inputData.value = "";
}
})
첫 번째는 바로 function의 파라미터로 들어가있는 event이다. event는 keypress라는 이벤트 레퍼런스를 통해 이벤트가 발생했을 때, 그 이벤트로 발생한 값을 받아 함수 내부에서 사용하기 위해 사용하는 파라미터이다. keypress는 키보드의 쉬프트, Fn, CapsLock키를 제외한 키가 눌렸을 때 연속적으로 실행되는 이벤트 방식이고, event.keyCode를 통해 event가 발생했을 때, 어떤 키가 눌렸는지 판단할 수 있게 된다. 엔터의 keyCode를 확인하는 방법에는 console.log(event);를 통해 자신이 입력한 키보드의 키가 무슨 keyCode를 가지고 있는지 확인하는 것과, 아래의 사이트에서 확인하는 방법이 있다. www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes 엔터의 경우에는 keyCode가 13이므로, 입력된 keyCode가 13일 때, 리스트에 추가하는 동작이 실행되는 것이다.
2. 취소선 만들기
리스트를 삭제하는 것보다 취소선 만들기를 먼저 해보겠다. 취소선 만들기는 간단하다. 자바스크립트의 HTML DOM에는 미리 onclick이라는 이벤트 함수가 정의되어 있다. onclick 함수를 통해 ul에 있는 li를 선택해서, style.css에 정의되어 있는 done 클래스를 toggle 기능을 통해 on/off 시켜주면 된다.
다음과 같이 코드를 작성하고 개발자 도구에서 어떤 메시지가 뜨나 확인해보자. onclik은 유저가 해당 요소를 클릭했을 때 발생하는 이벤트이다.
ul.onclick = function(event){
console.log(event);
}
리스트의 B를 클릭했을 때, 개발자도구에서 MouseEvent라는 메시지가 나오고 역삼각형 모양의 아이콘을 누르면 메뉴를 펼칠 수 있다. 여기서 target: li를 클릭해서 또 메뉴를 펼치고 아래로 쭉 내려오면 다음과 같이 outerHTML : "<li>B</li>"
라는 항목을 볼 수가 있다. 막연하게 event라고 지정하면 저렇게 수많은 정보들이 출력되는데, 우리가 원하는 것은 우리가 클릭한 요소의 정보이다. 그래서 event.target이라는 함수를 이용하면 클릭한 요소의 정보만을 사용할 수가 있게 된다. 이제 코드를 다음과 같이 수정하고 다시 결과를 확인해보자.
ul.onclick = function(event){
console.log(event.target);
}
//onclick 함수는 이벤트리스너에 "click"을 사용하는 것과 동일한 기능을 한다
//이벤트리스너를 이용한 동일한 기능을 하는 함수
function onClickEvent(event){
console.log(event.target);
}
ul.addEventListener("click", onClickEvent);
클릭한 요소들만 따로 접근하는 방법을 알았으니 취소선을 만드는 일은 정말 어렵지 않다. DOM의 classList 선택자의 toggle 기능을 이용해서 미리 style.css에 정의해놓은 done 클래스를 on/off 하기만 하면 된다.
ul.onclick = function(event){
event.target.classList.toggle("done");
}
3. 리스트 삭제하기
마지막으로 리스트를 삭제할 수 있는 삭제 버튼을 만들어야 한다. 미리 만들어져 있는 리스트 A ~ E 까지는 index 파일에 직접 <button> 태그를 이용해서 버튼을 만들어주도록 하겠다.
<ul>
<li>A <button class = "delete">삭제</button></li>
<li>B <button class = "delete">삭제</button></li>
<li>C <button class = "delete">삭제</button></li>
<li>D <button class = "delete">삭제</button></li>
<li>E <button class = "delete">삭제</button></li>
</ul>
이 상태에서는 당연하게 아무리 삭제 버튼을 클릭해도 이벤트가 발생하지 않는다. 하지만 문제가 있다면, 아래 그림과 같이 삭제 버튼에도 취소선이 그어진다는 것이다.
이러한 문제점을 해결하기 위해선 두 가지가 필요하다. 첫 번째는 ul의 자식으로 들어간 li들에 클래스를 달아주고, 취소선을 그어주는 onclick 함수에서 event.target의 클래스가 li일 경우에만 취소선 toggle 작동되게 하는 것이다.
<ul>
<li class="li">A <button class = "delete">삭제</button></li>
<li class="li">B <button class = "delete">삭제</button></li>
<li class="li">C <button class = "delete">삭제</button></li>
<li class="li">D <button class = "delete">삭제</button></li>
<li class="li">E <button class = "delete">삭제</button></li>
</ul>
index 파일을 위와 같이 수정하고, script 파일에서는 아래와 같이 수정한다.(토글을 눌러 style에 있는 done 클래스를 활성화시키면, 원래 있던 li 클래스 옆에 done 클래스가 새로 생기기 때문에 취소선을 없애기 위해 || 를 사용했다.)
ul.onclick = function (event) {
if (event.target.className === "li" || event.target.className==="li done") {
event.target.classList.toggle("done");
}
}
두 번째는, 이렇게 코드를 수정하면 먼저 생성된 리스트인 A ~ E 까지는 삭제 버튼을 클릭해도 취소선이 생기지 않지만, 이벤트리스너를 통해 새롭게 추가되는 리스트들의 클래스들을 설정해주지 않았다. 그렇기 때문에 새로운 리스트를 생성해주는 함수에다 className = "li"; 라는 코드를 추가해 새롭게 생성되는 리스트에도 클래스를 설정해 줄 수 있다.
*DOM에는 클래스를 설정해주는 선택자가 여러 개 존재하는데, classList.add를 사용해도 클래스를 추가시킬 수 있다. className의 경우, 새로운 클래스를 생성하는 것은 아니지만 이미 존재하는 클래스의 이름을 덮어씌울 수 있기 때문에 사용에 주의가 필요하다. classList.add의 경우에는 덮어 씌우지 않으며, 이름이 같은 클래스가 이미 존재한다면 새롭게 추가하지 않는다.
inputData.addEventListener("keypress", function (event) {
if (inputData.value.length > 0 && event.keyCode === 13) {
var li = document.createElement("li");
li.appendChild(document.createTextNode(inputData.value));
li.className = "li"; // 추가되는 리스트의 클래스 설정
ul.appendChild(li);
inputData.value = "";
}
})
미리 생성되어 있는 리스트에 삭제 버튼을 생성하고 취소선에 대한 추가적인 설정까지 해주었으면 이제 새롭게 생성되는 리스트에 버튼을 생성해줄 차례이다.
inputData를 받아와서 리스트에 추가해주는 시점에 삭제 버튼을 추가하는 것이 가능해 보인다. 따라서 리스트를 생성해주는 코드에 새롭게 내용을 추가해주자. 그러면 리스트가 추가되면서 삭제 버튼도 함께 추가되는 것을 볼 수 있다.
inputData.addEventListener("keypress", function (event) {
if (inputData.value.length > 0 && event.keyCode === 13) {
var li = document.createElement("li");
li.appendChild(document.createTextNode(inputData.value));
li.className = "li"; // 추가되는 리스트의 클래스 설정
var btnDel = document.createElement("button");
btnDel.appendChild(document.createTextNode("삭제"));
li.appendChild(btnDel);
ul.appendChild(li);
inputData.value = "";
}
})
이제, 삭제 버튼을 클릭하면 리스트를 삭제하는 기능을 추가해줄 차례이다. 아래의 index 파일 내부의 구조를 살펴보자.
구조를 보면 <ul> 태그의 자식으로 <li> 태그가 들어있고, <li> 태그의 자식으로 <button> 태그가 있는 모습이다. 그렇다면 삭제 버튼을 클릭했을 때, 해당 삭제 버튼의 부모 태그를 삭제한다면 리스트와 함께 버튼이 사라질 것이라는 추측을 할 수 있게 된다.
취소선을 긋기 위해 event.target을 사용했다. 그리고 이 event.target을 이용하면 취소 버튼을 구분할 수 있고, 그렇게 된다면 <button> 태그의 부모인 <li> 태그에도 쉽게 접근할 수 있을 듯하다. 그래서 먼저 삭제 버튼을 클릭했을 때, 부모에 접근이 가능한 지부터 확인해보겠다.
btnDel.addEventListener("click", checkParent);
버튼을 추가하는 코드 아래에 다음과 같이 이벤트리스너를 달아주고 checkParent라는 함수를 만들어서 클릭하면 부모의 클래스를 반환하는 함수를 만들어 보도록 하겠다. checkParent 함수는 다음과 같이 작성한다.
function checkParent(event) {
return console.log(event.target.parentNode.className);
}
그리고 결과값을 살펴보자. 취소선을 긋는 방법과 마찬가지로 역시 target을 이용해서 부모 태그의 클래스 이름을 알 수 있다. 그렇다면 이제 checkParent의 이름을 removeParentNode로 바꾸고 삭제 버튼을 클릭하면 부모 태그를 삭제하는 동작을 추가하기만 하면 된다.
function removeParentNode(event) {
var target = event.target;
target.removeEventListener("click", removeParentNode);
target.parentNode.remove();
}
마우스 클릭으로 이벤트가 발생하면, 마우스가 클릭한 타겟을 변수로 삼는다. 그러고 버튼에 붙어있던 이벤트리스너를 제거해주기 위해 removeEventListener를 사용하여 부모 태그를 삭제하기 전에 버튼을 깨끗하게 비워준다. 그리고 나서 버튼의 부모를 제거한다.
그리고 처음에 index에서 생성한 삭제 버튼에도 마찬가지로 removeParentNode 함수를 추가해주기 위해 다음과 같은 코드를 추가해준다.
for (var i=0; i<deleteBtn.length; i++) {
deleteBtn[i].addEventListener("click", removeParentNode);
}
그러면 간단한 To Do List의 기능을 모두 구현할 수 있게 된다.