-
[JS/DOM] 바닐라 자바스크립트로 간단한 To Do List 만들어보기 - 피드백추가Frontend 2019. 2. 16. 22:32
회사에서는 대부분 jQuery를 사용해서
웬만한 이벤트 구현을 하다보니
그냥 자바스크립트로는 아무것도 할 수 없게 되었다.
아주 간단한 기능인데도
순수하게 자바스크립트로만 구현하려니
꽤나 머리가 아프다..ㅠㅠ
이렇게 제이쿼리에 의존하고 있었다니..
많이 반성하며 요즘 열심히 공부하고 있는 중이다.
매주 일요일에 자바스크립트 스터디를 하고 있는데
지난번 스터디 때 Dom에 대해서 배웠고 그 부분을
연습해보고자 간단한 To Do List를 구현해보기로 하였다!
일단 아래와 같이 모양을 만들어주었다.
예제지만 괜히 이쁘게 만들고 싶은 마음에 웹폰트도 검색해서
나름 예뻐보이는 걸로 넣어보았다ㅋㅋ
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <title>To Do List</title> <link href="https://fonts.googleapis.com/css?family=Gamja+Flower" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Nanum+Gothic" rel="stylesheet"> <style> html, body, input { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Gamja Flower', cursive; } button { font-family: 'Gamja Flower', cursive; background: #a369af; color: #fff; font-size: 18px; cursor: pointer; } .list-box { width: 500px; margin: 100px auto; border: 1px solid #eee; padding: 20px 30px 50px; background: #333; } .list-box h1 { padding-bottom: 10px; text-align: center; color: #9fd6c2; border-bottom: 1px solid #87898f; } .write-box { width: 100%; height: 35px; font-size: 0; } .write-box input { width: 400px; font-size: 20px; border: none; padding: 0 10px; height: 100%; } .write-box button { width: 100px; border: none; height: 100%; } .list-table { border-spacing:0px; border-collapse: collapse; width: 100%; margin: 20px 0; } th, td { border: 1px solid #ddd; padding: 10px 20px; font-size: 20px; } th { background: #9fd6c2; } td { color: #fff; } tbody td:first-child { text-align: center; } .btn-area { text-align: center; } .btn-area button { height: 35px; padding: 0 10px; border: none; } </style> </head> <body> <div class="list-box"> <h1>To Do List</h1> <div class="write-box"> <input type="text" class="text-basic"> <button type="button" id="btnAdd">추가</button> </div> <table class="list-table"> <colgroup> <col width="10%"> <col width="90%"> </colgroup> <thead> <tr> <th>check</th> <th>To do List</th> </tr> </thead> <tbody id="listBody"> <tr> <td> <input type="checkbox" class="btn-chk"> </td> <td> 청소하기 </td> </tr> <tr> <td> <input type="checkbox" class="btn-chk"> </td> <td> 숙제하기 </td> </tr> </tbody> </table> <div class="btn-area"> <button type="button" id="DeleteSel">선택 삭제</button> <button type="button" id="btnDelLast">마지막 항목 삭제</button> <button type="button" id="btnDelAll">전체 삭제</button> </div> </div> </body> </html>
이제 구현해야될 사항은 다음과 같다.
1. 인풋 텍스트에 할 일을 적고 '추가'버튼을 클릭하면 해당 내용이 담긴 행이 맨 밑에 만들어짐.
2. 인풋 텍스트에 아무것도 적지 않고 '추가'버튼을 클릭하면 alert메시지가 출력됨.
3. 체크박스를 체크하고 '선택삭제'버튼을 누르면 해당 행이 삭제됨.
4. '마지막 항목 삭제'버튼을 누르면 테이블 마지막 행이 삭제됨.
5. '전체삭제'버튼을 누르면 모든 행이 삭제됨.
시작해볼까!
*덧붙임: 아래 모든 코드들은 정답이 아닙니다. 저 기능들을 구현해보고자 몇시간 동안 혼자 고민하고 삽질한 결과물이므로 잘못된 방향의 코드들이 보이면 얼마든지 댓글로 피드백주세요.
document.getElementById('btnAdd').addEventListener('click', addList); // 추가 document.getElementById('btnDelAll').addEventListener('click', delAllEle); // 전체삭제 document.getElementById('btnDelLast').addEventListener('click', delLastEle); // 마지막 요소 삭제 document.getElementById('DeleteSel').addEventListener('click', delSelected); // 선택 삭제 // 추가 function addList() { } // 전체삭제 function delAllEle() { } // 마지막 항목 삭제 function delLastEle() { } // 선택 삭제 function delSelected() { }
일단 버튼들에게 click 이벤트를 걸어주고 함수를 선언했다.
[1] 행추가
function addList() { var contents = document.querySelector('.text-basic'); if (!contents.value) { alert('내용을 입력해주세요.'); contents.focus(); return false; } var tr = document.createElement('tr'); var input = document.createElement('input'); input.setAttribute('type', 'checkbox'); input.setAttribute('class', 'btn-chk'); var td01 = document.createElement('td'); td01.appendChild(input); tr.appendChild(td01); var td02 = document.createElement('td'); td02.innerHTML = contents.value; tr.appendChild(td02); document.getElementById('listBody').appendChild(tr); contents.value=''; contents.focus(); }
행 추가 함수 addList()이다.
먼저 인풋박스의 value값이 비어있으면 alert()로 메시지를 띄우고
인풋박스에 focus를 준 후 함수를 중단한다.
그리고 tr과 td, input을 새로 생성한다.
document.createElement()를 사용해서 생성하면 됨.
제이쿼리는 넘나 쉽게 생성하고 append()할 수 있는데
자바스크립트는 절차가 참 복잡하다.
각각 생성한 후 input에는 type="checkbox", class="btn-chk"라는 속성도 부여해야하니까
setAttribute()를 이용해서 속성을 주었다.
그리고 td안에 아까 변수 contents에 저장한 값을 넣고
tr에 append시킴.
그리고 각각 조합해서 tbody에 append시키고
인풋박스의 value는 '' 빈값으로 초기화시켜주었다.
이렇게 잘 작동이 된다!
[2] 전체삭제
function delAllEle() { var list = document.getElementById('listBody'); var listChild = list.childNodes; for(var t of listChild) { if (t.nodeType == 1) { list.removeChild(t); } } }
그리고 전체삭제하기!
여기서 매우 헤맸는데...
일단 listBody라는 id를 가진 tbody의 childNodes를 listChild라는 변수에 저장했다.
그리고 console로 출력했더니 tbody의 자식 노드들이 전부 유사배열로 들어온다.
걔네들을 for of 문으로 돌려서 각각의 노드들의 nodeType을 검사한다.
nodeType이 1이면 Dom이라는 뜻이니까 걔네들 list에서 remove시킨다.
사실 mdn을 뒤져보니 이거보다 매우 간단한 방법이 있었는데
while문을 사용하여 전체 삭제하는 방법이었다.
// 전체삭제 function delAllEle() { var list = document.getElementById('listBody'); while(list.firstChild) { list.removeChild(list.firstChild); }
바로 이렇게...
list의 첫번째 자식이 있을 때까지 list에서 그 첫번째자식을 계속 삭제시키는 방법이다.
그러면 모두 삭제되서 모든 자식이 삭제될 때까지 계속 삭제할테니까 결국 전체가 삭제되겠지..
난 위에 써놓은 코드 짜느라고 3시간을 허비했는데
이렇게 심플하고 간단한 방법이 있다니! 너무 간단하여 허무했음.
+추가 수정 190217)
내가 짠 코드에 엄청난 오류가 있었다!
첫번째는 나는 document.getElementById('listBody')로 부모 tbody를 가져온 후
그 tbody의 childNodes를 가져와서 반복문을 돌렸는데,
childNodes는 element node뿐만 아니라 태그와 태그 사이의 공백들을 text node로 같이 가져온다!
그래서 쓸데없는 공백까지 반복문을 돌리게 되고 nodeType 검사까지 해야하므로 매우 비효율적인 방법으로 코드를 쓴 것이다.
두번째는 반복을 돌리면서 removeChild를 해줬다는거.
removeChild를 해주면서 childNodes가 하나씩 삭제되어 한칸씩 밀리는 현상이 발생했다.
그래서 테이블의 행을 여러 개 추가하고 전체 삭제를 누르면
다 삭제되지 않고 몇개가 남는 현상이 발생했다는거.
그래서 다시 수정한 코드는 다음과 같다.
function delAllEle() { var list = document.getElementById('listBody'); var listChild = list.children; for(var i=0; i<listChild.length; i++) { list.removeChild(listChild[i]) i--; } }
childNodes가 아니라 children이라고 쓰면
하위 element요소만 받아올 수 있다!
그 하위 요소들을 for문 돌려서 지우되,
지운 순간 i도 같이 하나씩 지워준다.
그래야 순서가 밀려서 다 안지워지는 일이 없어지니까!
[3] 마지막요소 삭제
function delLastEle() { var body = document.getElementById('listBody'); var list = document.querySelectorAll('#listBody > tr'); var liLen = list.length-1; body.removeChild(list[liLen]); }
그리고 마지막 요소 삭제하기!
이번엔 tbody의 tr을 querySelectorAll로 가져와서
그 리스트의 length에서 1을 뺀다.
그러면 리스트의 맨 마지막 요소 방번호가 되니까 걔를 삭제하면 마지막 요소가 삭제되겠지..
이것도 잘 작동은 된다. 코드가 효율적인지는 잘 모르겠으나.. 일단 되면 된거지..
function delLastEle() { var body = document.getElementById('listBody'); var list = document.querySelectorAll('#listBody > tr'); if(list.length > 0) { var liLen = list.length-1; body.removeChild(list[liLen]); } else { alert('삭제할 항목이 없습니다.') return false; } }
수정!)
생각해보니 마지막 요소가 없는 경우에는 동작을 중단시켜줘야 동작을 안한다!
list의 length를 체크해서 0보다 큰 경우에만 실행시키고
모두 삭제되어 0이 되는 경우에는 return false;를 적어준다.
[4] 체크박스 선택 삭제
function delSelected() { var body = document.getElementById('listBody'); var chkbox = document.querySelectorAll('#listBody .btn-chk'); for(var i in chkbox) { if(chkbox[i].nodeType == 1 && chkbox[i].checked == true) { body.removeChild(chkbox[i].parentNode.parentNode); } } }
그리고 체크박스 선택한 행만 삭제하기.
이건 체크박스를 다 가져온 후 걔네를 for문으로 돌려서 console로 출력해봤더니
checkbox dom만 출력되는게 아니어서 nodeType을 또 검사해주었다.
그리고 checkbox가 checked된 상태인지 판단해주기.
그리고 nodeType이 1이고 checked 상태가 true이면
해당 체크박스의 parentNode(->td)의 parentNode(->tr)을 삭제해준다.
이렇게 하면 체크박스로 선택한 행만 삭제가 된다!
와우!
jQuery로 하면 10분만에 대충 만드는걸
바닐라 자바스크립트로 5시간은 넘긴것 같다.ㅎ........
내일 스터디 선생님이 잘못된 점 있는지 피드백해주시면 이 글에다 업데이트할 예정임.
반응형'Frontend' 카테고리의 다른 글
[JS] 자바스크립트 7일차 요점정리 (252) 2019.02.17 [JS] 자바스크립트 6일차 요점정리 (251) 2019.02.17 [CSS] text-decoration: underline 간격 조금 띄우기 (252) 2019.02.15 [jQuery] 키보드 이벤트 (동시에 2개 키 누를 때 이벤트) (251) 2019.02.15 [CSS] input[type="password"] 비밀번호 스타일 지정 (252) 2019.02.14 COMMENT