JavaScript로 Todo List 만들기

박지윤·2023년 10월 24일
0

JavaScript

목록 보기
13/13
post-thumbnail

자바스크립트를 공부하면서, 꼭 한 번 만들어보고 싶었던 Todo List를 만들어보았다.

1. 구현 기능

  • 투두리스트에 들어가면 Input에 Focus
  • 엔터 버튼 혹은 + 버튼을 누르면 일정 추가
  • 일정을 입력하지 않은 상태에서 일정을 추가할 경우 경고창 발생
  • 완료된 일정에 밑줄 스타일 추가
  • 휴지통 버튼을 누르면 일정 삭제

2. HTML

<body>
  <div id="wrap">
    <h1 id="title">To Do List</h1>
    <div id="inputBox">
      <form>
        <input type="text" id="todoInput" placeholder="Add New Task" value="">
        <button id="addBtn" type="button"><i class="fa-solid fa-plus"></i></button>
      </form>
    </div>
    <div id="todoBox">
      <ul id="todoList">
      </ul>
    </div>
  </div>
</body>

3. CSS

1) Background

body {
  width: 100%;
  height: 883px;
  background-image: linear-gradient(135deg, #654ea3, #ebc9c9);
  background-repeat: no-repeat;
}

보통 웹사이트에서는 연한 단색 배경이나, 하얀 배경을 많이 사용하기 때문에 이번 미니 프로젝트에서는 단색 대신 그라데이션 배경을 사용해보았다.

그라데이션 코드 사이트들을 보면서, 꼭 사용해보고 싶다는 생각이 들었었는데 마음에 드는 것 같다 😊

<그라데이션 추천 사이트>
1. https://webgradients.com/
2. https://uigradients.com/
3. https://csshero.org/mesher/

2) Input

#todoInput {
  width: 390px;
  height: 55px;
  outline:none;
  box-sizing: border-box;
  padding:0 20px;
  border-radius: 5px;
  border: 1px solid #bbb;
  color: #666;
  font-family: 'Noto Sans KR', sans-serif;
  font-size: 14px;
}

outline:none : Input을 선택하면 나타나는 테두리를 없애줌
box-sizing: border-box : padding, margin, border의 크기를 width와 height에 포함하는 속성
이 속성을 준 상태에서 padding:0 20px을 하면 왼쪽 오른쪽 여백이 Input의 안쪽에 생성된다.
즉, Input에 입력되는 텍스트는 왼쪽 오른쪽으로 20px의 여백을 가지게 된다.

3) addBtn

#addBtn {
  width: 80px;
  height: 55px;
  border-radius: 5px;
  border: none;
  background-color: #654ea3;
  color: #fff;
  transition:0.3s;
}

#addBtn:hover {
  cursor: pointer;
  background-color: #ac9ecf;
}

:hover : 마우스를 가져가면 변경될 스타일을 지정. 여기에서는 커서와 버튼 색이 변경된다.

4) text

.text {
  width: 82%;
  display: inline-block;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

overflow: hidden : 자식 요소를 부모 요소의 크기만큼만 보이게 한다.
white-space: nowrap: 부모요소보다 텍스트의 길이가 길어도 줄바꿈이 일어나지 않게 한다.
text-overflow : 텍스트가 부모 요소보다 길면, 자르고 뒷 부분을 ...으로 표기해 생략한다.

위의 세 가지 속성을 모두 써주어야 이렇게 텍스트 뒷부분이 ...으로 표기된다!

4) js

1) Input에 Focus + 일정 추가


window.onload = function () {
  todoInput.focus();  // 처음 들어올 때 input에 focus

  function addTodo() {
    let todoText = document.getElementById("todoInput");
    let todoList = document.getElementById("todoList"); 

    if (todoText.value == "") {
      alert("리스트를 입력해주세요.");

    } else {
      // 노드 생성하기
      let list = document.createElement("li");
      let todoListBox = document.createElement("div");
      let check = document.createElement("i");
      let text = document.createElement("span");
      let del = document.createElement("i");

      // 노드에 class 부여하기 
      todoListBox.className = "listBox";
      check.className = "fa-regular fa-circle nonchecked";
      text.className = "text";
      del.className = "fa-solid fa-trash-can";

      // 노드를 document에 추가하기
      // 부모노드.appendChild(자식노드);

      todoList.appendChild(list);
      list.appendChild(todoListBox);
      todoListBox.appendChild(check);
      text.innerHTML = todoText.value;    //todoInput의 value를 span에서 출력
      todoListBox.appendChild(text);
      todoListBox.appendChild(del);

      // 추가 후 input에 쓴 내용 초기화
      todoInput.value = "";

      // 버튼 이벤트 추가 
      check.addEventListener("click", checkTodo); // check 클릭하면 동작할 이벤트
      text.addEventListener("dblclick", checkTodoClick); // text 더블클릭하면 동작할 이벤트 
      del.addEventListener("click", delTodo); // del 클릭하면 동작할 이벤트
    }
  })

우선, todoInput.focus() 을 통해 홈페이지에 들어가면 Input 창에 포커스가 가도록 설정한다.
만약 Input에 값이 없을 경우 리스트를 입력하라는 경고창을 띄우고, 값이 있을 경우 createElement 를 통해 노드를 생성한다.

className을 통해 생성된 노드에 클래스를 부여하여 CSS에서 지정한 스타일을 적용한다.

스타일 지정까지 마친 노드는 appendChild를 통해 document에 추가한다.
여기서 appendChild 앞에 쓰인 것이 부모 노드, 괄호 안에 들어가 있는 것이 자식 노드가 된다.

즉, 위의 코드는 이러한 형태로 document에 출력된다.

<!--위에서 설정한 변수를 토대로 보여지는 형태 구현--> 
<todoList>
  <list>
    <todoListBox>
      <check></check>
      <text>input value</text>
      <del></del>
    </todoListBox>
  </list>
</todoList>


<!--실제 코드--> 

<ul id="todoList">
  <li>
    <div class="listBox">
      <i class="fa-regular fa-circle nonchecked"></i>
      <span class="text">input value</span>
      <i class= "fa-solid fa-trash-can"></i>
    </div>
  </li>
</ul>

노드가 추가 된 뒤에도 Input에 입력한 값이 남아있으면, 리스트를 추가할 때 번거롭기 때문에 todoInput.value = "" 을 통해 Input의 값을 초기화 하고, 리스트가 추가되면서 생성되는 버튼과 관련된 이벤트들을 추가해준다.

2) 리스트 생성 이벤트

// EventTarget.addEventListener(type, listener)

// add버튼 클릭하면 동작할 이벤트
addBtn.addEventListener("click", addTodo)

//input 창에서 엔터 누르면 동작할 이벤트
todoInput.addEventListener("keypress", function (e) {
  if (e.keyCode === 13) { // enter키의 keyCode
    e.preventDefault();  // form 태그에 input 태그가 하나면 자동으로 submit. 이를 방지 하기 위해 추가
    addTodo();
  }
});

특정 이벤트에서 위에서 정의한 addTodo()가 실행되게 하기 위해서는 이벤트를 추가해야 한다.
이럴 때 사용하는 것이 addEventListener 다.

이번 미니 프로젝트에서는 add버튼 뿐만 아니라 엔터를 누르면 리스트가 추가되도록 이벤트를 설정했다.


3) 리스트 체크

// check 클릭하면 동작할 이벤트
function checkTodo(e) {
  let check = e.target;
  let text = e.target.nextSibling;

  if (check.className.indexOf("nonchecked") > -1) { // 체크가 안 된 상태에서 클릭하면 체크 + 텍스트 스타일
    check.className = "fa-solid fa-circle-check checked";
    text.style.textDecoration = "line-through";
    text.style.color = "#bbba";
  } else {                                         //  아니면 체크 해제 + 텍스트 스타일
    check.className = "fa-regular fa-circle nonchecked";
    text.style.textDecoration = "none";
    text.style.color = "#333"
  }
}

// text 더블클릭하면 동작할 이벤트 
function checkTodoClick(e) {
  let text = e.target;
  let check = e.target.previousSibling;

  if (check.className.indexOf("nonchecked") > -1) { // 체크가 안 된 상태에서 클릭하면 체크 + 텍스트 스타일
    check.className = "fa-solid fa-circle-check checked";
    text.style.textDecoration = "line-through";
    text.style.color = "#bbba";
  } else {                                         //  아니면 체크 해제 + 텍스트 스타일
    check.className = "fa-regular fa-circle nonchecked";
    text.style.textDecoration = "none";
    text.style.color = "#333"
  }
}

<todoListBox>
  <check></check>
  <text>input value</text>
  <del></del>
</todoListBox>

checkTodo()checkTodoClick()은 체크 상태를 확인하고 체크 및 체크 해제를 해주는 함수이다.

checkTodo()는 Event Target이 check 버튼이기 때문에 checke.target으로 texte.target.nextSibling으로 설정하였다.

반대로, checkTodoClick()은 Text가 Event Target이기 때문에 texte.target으로, checke.target.previousSibling으로 설정하였다.

여기서 nextSibling은 선택된 요소의 형제 요소들 중 바로 다음에 있는 형제 요소
previousSibling은 선택된 요소의 형제 요소들 중 바로 전에 있는 형제 요소 를 말한다.

리스트의 체크 상태를 확인하는 방법으로는 indexOf를 사용하였다.
indexOf 는 입력한 문자열이 있을 경우, 그 문자열이 처음 나타난 위치 인덱스를 반환하고 없을 경우 -1을 반환하는 메소드이다.

이를 활용하여 check버튼의 Class에 nonchecked가 있다면 checked로, 없을 경우 nonchecked로 check의 class를 바꾸어주었다. 시각적인 효과를 더욱 극대화 하기 위해 글씨 가운데 선과 글자색 변화도 함께 추가해주었다.

4) 리스트 삭제

// del 클릭하면 동작할 이벤트
function delTodo(e) {
  let list = e.target.parentElement.parentElement; //del의 부모요소(div)의 부모요소(li)
  list.remove();
}
<todoList>
  <list>
    <todoListBox>
      <check></check>
      <text>input value</text>
      <del></del>
    </todoListBox>
  </list>
</todoList>

delTodo()는 리스트를 삭제하는 함수이다.
list는 del 버튼의 부모 요소의 부모요소 이기 때문에 parentElement 를 두 번 써주었다.

5. 느낀점

미니 프로젝트를 끝내자마자 느낀 점을 썼다면 혼자 이 코드를 구현했다는 게 참 뿌듯하다고 썼을 것 같다. 하지만 한달이 지난 지금 보니 코드를 조금 복잡하게 구성했다는 느낌이 든다. 아무래도 지금까지 배운 것을 복습한다는 느낌으로 미니 프로젝트를 구상해서 그런 것 같다.

전체 선택, 전체 삭제 등의 기능도 추가 되면 좋을 것 같다.

다음에는 제이쿼리나 리액트를 사용해 지금보다 가독성 있고, 쓸모 있는 투두 리스트를 만들어 보고 싶다 :)

profile
프론트엔드 개발 및 실무 프로젝트 구현과정 수료

0개의 댓글