본문 바로가기
JavaScript

[JavaScript] Todo list 만들기

by teamnova 2023. 7. 24.
728x90

안녕하세요 오늘은 js로 투두리스트 만드는 법을 알아보겠습니다. 일전에도 투두리스트에 대해 만든 적이 있는데요 (이전 포스트 참고바랍니다.), 오늘은 이전과 달리 저장은 local Strorage를 사용할 것이고(localStroage포스트 참고) 완료가 될 때마다 게이지로 표시하여 달성률을 시각적으로 보여주도록 해보겠습니다. 

 

완성 시 아래와 같이 동작합니다.

 

투두리스트 완성 시 모습

 

 

아래는 전체 코드입니다.

 

todo.html

 

<h1>미니 투두리스트</h1>
<form id="todo-form">
  <input type="text" placeholder="Add a new task">
  <button type="submit">추가</button>
  <div id="progress-bar">
  <div id="progress"></div>
</div>
</form>
<ul id="todo-list">
</ul>
<div id="congrats">
  <h2>축하합니다!</h2>
  <p>해야할 일을 모두 완료했습니다.</p>
</div>

 

 

todo.css

 

body {
  font-family: Arial, sans-serif;
  margin: 0;
  padding: 0;
}

h1 {
  text-align: center;
  margin-top: 50px;
}

form {
  text-align: center;
  margin-top: 30px;
}

input[type="text"] {
  padding: 10px;
  font-size: 16px;
  border-radius: 5px;
  border: none;
  box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
}

input[type="checkbox"]:checked + span {
  text-decoration: line-through;
}

button {
  padding: 10px;
  font-size: 16px;
  border-radius: 5px;
  border: none;
  background-color: #4CAF50;
  color: white;
  cursor: pointer;
  box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
}

button:hover {
  background-color: #3e8e41;
}

ul {
  list-style-type: none;
  padding: 0;
}

li {
  display: flex;
  align-items: center;
  margin-bottom: 10px;
  font-size: 16px;
}

li input[type="checkbox"] {
  margin-right: 10px;
}

#progress-bar {
  width: 30%;
  height: 20px;
  margin: auto;
  background-color: #f2f2f2;
  margin-top: 30px;
  bottom: 30px;
  left: 0;
  right: 0;
}


#progress {
  height: 20px;
  background-color: #4CAF50;
  width: 0%;
  transition: width 0.5s ease;
}

#congrats {
  display: none;
  text-align: center;
  margin-top: 50px;
}

#congrats h2 {
  font-size: 32px;
}

#congrats p {
  font-size: 20px;
}

#todo-list {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

 

todo.js

 

// HTML에서 필요한 요소들을 가져오기
const form = document.getElementById("todo-form"); // Todo Form
const input = document.querySelector("input"); // Todo 입력 필드
const ul = document.getElementById("todo-list"); // Todo List
const progressBar = document.getElementById("progress"); // ProgressBar
const congrats = document.getElementById("congrats"); //전부 완료시 보여지는 축하 메시지

// Todo List를 저장할 배열
let todos = [];

// Local Storage에서 저장된 Todo List를 불러와서 todos 배열에 저장한다
const fetchTodos = () => {
  todos = JSON.parse(localStorage.getItem("todos")) || [];//"todos"가 키값
}

// Todo List에 새로운 Todo를 추가한다
const addTodo = () => {

  //0.입력 필드의 값이 비어있는 경우 함수를 종료.(추가할 필요가 없기 때문)
  if (input.value.trim() === "") {
    return;
  }
  
  //1-1.입력값이 있는경우 새로운 Todo 객체를 생성
  const todo = {
    title: input.value,
    completed: false
  };
  
  //1-2.todos 배열에 새로운 Todo를 추가
  todos.push(todo);
  
  //1-3.Local Storage에 todos 배열을 저장
  localStorage.setItem("todos", JSON.stringify(todos));
  
  //1-4.입력 필드를 초기화한다
  input.value = "";
  
  //2.Todo List를 다시 렌더링한다
  renderTodos();
}

// Todo List에서 Todo를 삭제
const removeTodo = (index) => {
  // todos 배열에서 해당 인덱스의 Todo를 삭제
  todos.splice(index,1);
  // Local Storage에 todos 배열을 저장
  localStorage.setItem("todos", JSON.stringify(todos));
  // Todo List를 다시 렌더링
  renderTodos();
}

// Todo List에서 Todo를 체크한다
const toggleCompleted = (index) => {
  // 해당 인덱스의 Todo의 completed 속성을 반전시켜 값 얻기(true,false)
  todos[index].completed = !todos[index].completed;
  
  // Local Storage에 todos 배열을 저장한다
  localStorage.setItem("todos", JSON.stringify(todos));
  
  // ProgressBar를 업데이트한다
  updateProgressBar();
  // 모든 Todo가 완료되었는지 체크한다
  checkCompleted();
}

// Todo List에서 하나의 Todo를 렌더링한다
const renderTodo = (todo, index) => {
  // Todo 항목을 담을 li 요소를 생성한다
  const li = document.createElement("li");
  
  // Todo 항목의 체크박스를 생성한다
  const checkbox = document.createElement("input");
  checkbox.type = "checkbox";
  checkbox.checked = todo.completed;
  checkbox.addEventListener("change", () => toggleCompleted(index));
  
  // Todo 항목의 제목을 생성한다
  const span = document.createElement("span");
  span.innerText = todo.title;
  
  // Todo 항목의 삭제 버튼을 생성한다
  const deleteBtn = document.createElement("button");
  deleteBtn.innerText = "X";
  deleteBtn.addEventListener("click", () => removeTodo(index));
  
  // li 요소에 체크박스, 제목, 삭제 버튼을 추가한다
  li.appendChild(checkbox);
  li.appendChild(span);
  li.appendChild(deleteBtn);
  
  // Todo List(ul 요소)에 li 요소를 추가한다
  ul.appendChild(li);
}

// Todo List 전체 렌더링하기
const renderTodos = () => {
ul.innerHTML = ""; // ul 엘리먼트의 innerHTML을 빈 문자열로 초기화
todos.forEach(renderTodo); // todos 배열에 저장된 각각의 Todo를 renderTodo 함수를 이용하여 li 엘리먼트로 변환하여 ul 엘리먼트에 추가
updateProgressBar(); // ProgressBar 업데이트
checkCompleted(); // 모든 Todo가 완료되었는지 체크하여, 축하 메시지를 보이거나 감춤
}

// ProgressBar 업데이트하기
const updateProgressBar = () => {
const completedCount = todos.filter(todo => todo.completed).length; 
// todos 배열에서 completed 속성이 true인 todo 개수를 세어 completedCount 변수에 할당
const percent = (todos.length > 0) ? (completedCount / todos.length) * 100 : 0;
// todos 배열의 크기가 0보다 크면, todos 배열에서 completed 속성이 true인 todo 개수의 비율을 계산하여 100을 곱함. 아니면, 0을 할당
progressBar.style.width = percent + "%";
// ProgressBar의 width 속성을 변경하여 완료된 Todo의 비율을 나타내는 막대의 길이를 조절
}

// 모든 Todo가 완료되었는지 체크하기
const checkCompleted = () => {
if (todos.length === 0) { // todos 배열의 크기가 0이면, 축하 메시지를 감춤
congrats.style.display = "none";
} else if (todos.every(todo => todo.completed)){ // todos 배열의 모든 todo가 completed 속성이 true이면, 축하 메시지를 보임
congrats.style.display = "block";
} else { // 그 외의 경우, 축하 메시지를 감춤
congrats.style.display = "none";
}
}

// 초기화
fetchTodos(); // Local Storage에서 Todo List 불러오기
renderTodos(); // Todo List 렌더링하기

// 폼 제출 이벤트 리스너 등록
form.addEventListener("submit", event => {
event.preventDefault(); // 기본 동작(새로고침) 방지
addTodo(); // Todo List에 새로운 Todo 추가하기
}); // 폼 제출 이벤트 리스너 등록하기

 

-자세한 내용은 아래 링크 참고 부탁드립니다. 

 

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/splice

 

Array.prototype.splice() - JavaScript | MDN

splice() 메서드는 배열의 기존 요소를 삭제 또는 교체하거나 새 요소를 추가하여 배열의 내용을 변경합니다.

developer.mozilla.org

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

 

Array.prototype.filter() - JavaScript | MDN

filter() 메서드는 주어진 함수의 테스트를 통과하는 모든 요소를 모아 새로운 배열로 반환합니다.

developer.mozilla.org

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/every

 

Array.prototype.every() - JavaScript | MDN

every() 메서드는 배열 안의 모든 요소가 주어진 판별 함수를 통과하는지 테스트합니다. Boolean 값을 반환합니다.

developer.mozilla.org

https://developer.mozilla.org/ko/docs/Web/API/Event/preventDefault

 

Event.preventDefault() - Web API | MDN

Event 인터페이스의 preventDefault() 메서드는 어떤 이벤트를 명시적으로 처리하지 않은 경우, 해당 이벤트에 대한 사용자 에이전트의 기본 동작을 실행하지 않도록 지정합니다.

developer.mozilla.org

https://developer.mozilla.org/ko/docs/Web/API/Window/localStorage

 

Window.localStorage - Web API | MDN

localStorage 읽기 전용 속성을 사용하면 Document 출처의 Storage 객체에 접근할 수 있습니다. 저장한 데이터는 브라우저 세션 간에 공유됩니다. localStorage는 sessionStorage와 비슷하지만, localStorage의 데이

developer.mozilla.org