투두리스트에서 내가 만들고 싶었던 기능은 다음과 같다.
- 리스트에 아이템을 추가 할 수 있을 것
- 아이템을 삭제할 수 있을 것
- 아이템을 완료하여 체크하여 표시할 수 있을 것
- 위 모든 사항이 페이지 새로고침을 해도 남아있을 것
<div class="todoInputContainer"> <form class="js-todoForm todoForm"> <input class="js-todoInput" type="text" placeholder="What are you going to do?" size="24" /> </form> <button class="js-addTodoBtn addTodoBtn" type="submit"> <i class="fas fa-plus-circle"></i> </button> </div> <ul class="js-todoList todoList"></ul> </div>
- 한 틀 안에 배치하기 위해
todoInputContainer
에<form>
,<button>
,<ul>
을 넣었다.- 클래스 이름을 두 개로 나눈 이유는 JS에서 사용할 이름과 CSS에서 사용할 이름을 구분하기 위함이다.
const todoForm = document.querySelector(".js-todoForm"); const todoInput = todoForm.querySelector(".js-todoInput"); const addTodoBtn = document.querySelector(".js-addTodoBtn"); const todoList = document.querySelector(".js-todoList"); const TODOS_LS = "todos"; let todos = [];
- HTML의 Class를 변수로 지정해준다.
TODOS_LS
의 LS는 Local Storage 이고 자세한건 아래에서 설명하겠다.let todos = [];
const
말고let
을 사용한 이유는 나중에 바뀌어야 하기 때문이다. 투두 아이템이 저장될 부분.
투두리스트에서 가장 먼저 할 일은? 리스트에 할 일을 추가하는 것이다.
추가를 하기 위해선 input
에 추가할 내용을 적을 것이고, 엔터를 누르거나 추가 버튼을 클릭해서 그 내용을 submit
해야 한다.
이 과정을 처리하는 함수를 살펴보자.
function todoHandleSubmit(event) { //prevent the form from submitting event.preventDefault(); const currentValue = todoInput.value; printTodo(currentValue); //empty the input todoInput.value = ""; }
- 빈
input
에 아무거나 넣어보면 화면이 새로고침이 되는 것처럼 된다. 이를 방지하기 위해preventDefault()
를 추가한다.const currentValue = todoInput.value;
-input
에 적혀있는 값을 변수로 선언한다.printTodo
함수에input
에 적혀있는 값을 포함하여 호출한다.todoInput.value = "";
호출 후input
을 비워준다.
input
에서 입력받은 내용은 어떻게 표시될지 아래에서 다뤄보자.
function printTodo(text) { //create li const todoLi = document.createElement("li"); todoLi.classList.add("todoLiClass"); //create complete button const completeBtn = document.createElement("button"); completeBtn.innerHTML = '<i class="fas fa-check"></i>'; completeBtn.classList.add("completeBtnClass"); completeBtn.addEventListener("click", completeBtnFunction); //create span const newTodo = document.createElement("span"); newTodo.innerText = text; newTodo.classList.add("newTodoClass"); //create delete button const deleteBtn = document.createElement("button"); deleteBtn.innerHTML = '<i class ="fas fa-trash"></i>'; deleteBtn.classList.add("deleteBtnClass"); //event listener deleteBtn.addEventListener("click", deleteBtnFunction); //create new id for todoLi and todoObj so they can be handled idividually const newId = todos.length + 1; todoLi.id = newId; // append created things in the list item todoLi.appendChild(completeBtn); todoLi.appendChild(newTodo); todoLi.appendChild(deleteBtn); //append the list item in the unordered list todoList.appendChild(todoLi); //create todo object to push in the todos array const todoObj = { text: text, id: newId, }; //push the object in the todos array todos.push(todoObj); //save the array in the local storage saveTodos(todos); }
- html에 있는
<ul>
에 리스트 아이템을 추가해야 하므로createElement("li")
로 리스트 아이템을 만들어줬고, 나중에 CSS에서 수정하기 위해classList.add("todoLiClass")
로 클래스 이름을 지정해주었다.- 삭제와 완료 기능을 하는 버튼도 추가해준다.
<span>
은 리스트 아이템의 텍스트 부분이 표시될 부분이다.newTodo.innerText = text;
의text
는 함수를 호출하며 입력되는 값을 받아온다.const newId = todos.length + 1;
나중에 완료하고 삭제할 때 특정id
의 아이템만 다뤄져야 하므로 추가한 부분이고 +1을 한 이유는todos
는array
이고 비어 있는 상태에서array
의 길이는 0이기 때문이다.appendChild
는 위에서 만든<button>
,<span>
을<li>
에 넣어주는 과정이다.- 그리고 이렇게 만들어진
<li>
를<ul>
에 넣어준다.todos.push(todoObj);
를 통해 배열에 오브젝트를 추가한다.saveTodos(todos);
오브젝트가 추가된 배열을Local Storage
에 저장하여 새로고침을 하여도 데이터가 남아있도록 한다.
다음으로 saveTodos
함수를 살펴보자.
function saveTodos() { localStorage.setItem(TODOS_LS, JSON.stringify(todos)); }
setItem
을 이용하여 TODOS_LS에string
형태로 넣어준다.Local Storage
에는 자바스크립트의 데이터는 저장될 수 없고string
만 저장할 수 있기 때문이다.
하지만 이렇게 저장했어도 새로고침을 하면 리스트에는 아이템이 없는 것처럼 보인다. 이것은 저장된 데이터를 불러오지 않았기 때문이다.
다음으로 저장된 데이터를 불러오는 함수를 살펴보자.
투두리스트가 가장 처음에 해야 할 일은 이미 추가된 아이템이 있는지 확인하고 불러와야 할 것이다.
그렇기 때문에 다음과 같은 함수로부터 출발할 수 있다.
function init() { loadTodos(); todoForm.addEventListener("submit", todoHandleSubmit); } init();
EventTarget
의addEventListner
메소드는 지정한 이벤트가 대상에 전달될 때마다 호출할 함수를 설정한다고 한다. 👉🏻DMN- 즉,
init()
함수는loadTodos()
함수를 호출 후,todoForm
에게submit
이벤트가 전달될 때todoHandleSubmit
함수가 호출된다는 의미이다.
이어서 loadTodos
함수를 살펴보자.
function loadTodos() { const loadedTodos = localStorage.getItem(TODOS_LS); if (loadedTodos !== null) { //string(loadedTodos) to object const parsedTodos = JSON.parse(loadedTodos); //run functions each time parsedTodos.forEach((itIsMeaningless) => { //write the extracted texts in todoInput printTodo(itIsMeaningless.text); }); } }
- 로컬스토리지에 저장이 된 데이터를 변수로 지정해준 것이
loadedTodos
이다.if
조건문을 통해loadedTodos
가 비어있지 않다면,input
에 아이템을 입력하여 프린트하는 내용이다.- 로컬 스토리지에 저장할 때
string
형태로 저장을 했으므로JSON.parse
를 이용해 오브젝트로 다시 되돌려준다.forEach
는 각 배열 요소에 각각에 대해 실행하는 메소드이다. 👉🏻DMNitIsMeaningless
부분은 아무거나 적어도 관계가 없어서..그냥...😏parsedTodos.forEach(function(anyName) { printTodo(anyName.text);});
이런 식으로 적어줄 수도 있다.printTodo(itIsMeaningless.text);
각 배열 요소의text
부분을printTodo
로 보내준다.- 이제 새로고침을 해도 리스트 아이템이 사라지지 않고 그대로 남아있게 된다.
printTodo
함수에서 각 버튼에 다음과 같은 이벤트 리스너를 추가해 두었다.
completeBtn.addEventListener("click", completeBtnFunction); deleteBtn.addEventListener("click", deleteBtnFunction);
이는
click
이벤트가 발생했을 때 해당 함수를 호출하게 해준다.
deleteBtnFunction
부터 보도록 하자
function deleteBtnFunction(event) { const todoItem = event.target; const li = todoItem.parentNode; li.classList.add("todoFall"); // wiat for the transition to end li.addEventListener("transitionend", function () { // todoList.removeChild(li); //this doesn't work properly with transitionend.. li.remove(); }); //clean todos from the local storage const cleanTodos = todos.filter(function (findIt) { //since li.id is not a number but a sting return findIt.id !== parseInt(li.id); }); todos = cleanTodos; saveTodos(); }
console.dir(event.tartget)
으로 해당 버튼의 부모노드를 찾아내서 버튼이 해당된 부모 리스트를 지워주면 버튼만 삭제되지 않고 리스트 자체가 삭제된다.const li = todoItem.parentNode;
이렇게 리스트 아이템이 들어있던 부모 리스트를 변수로 지정해준다.transitionend
를 사용한 이유 : 삭제될 때transition
으로 사라지는 효과를 CSS에서 주었는데 삭제와 동시에 이루어지기 때문에 애니메이션 효과를 볼 수 없었다. 그래서transition
이 끝나고 함수를 호출하게 해주는 메소드인transitionend
를 사용하였다.- 그리고 왜인지는 모르겠지만
transitioned
를 사용하지 않고removeChild(li)
를 사용하면 정상적으로 삭제가 잘 됐는데transitionend
와 함께 잘 작동하지 않는 것 같아서li.remove()
를 사용했다.- 겉으로 보여지는 리스트 아이템을 삭제했으니 로컬 스토리지에서도 삭제를 해주어야 하므로
filter
메소드를 사용했다.return findIt.id !== parseInt(li.id);
-Local Storage
는string
만을 저장하므로 숫자로 바꿔줄 필요가 있었다.parseInt
는 문자열을 정수로 바꿔준다.todos = cleanTodos;
로 삭제된 배열로 새로 업데이트해 주고saveTodos
로 업데이트 된 데이터를 저장한다.
function completeBtnFunction(event) { const todoItem = event.target.parentNode; if (todoItem.classList[0] === "todoLiClass") { todoItem.classList.toggle("todoCompleted"); } }
- 삭제 버튼과 마찬가지로 부모노드를 찾아서 변수로 정해주고 클래스 이름이 원하는 이름과 일치하면
todoCompleted
를 토글시키는 함수이다.- 완료 상태를 저장하기 위해서는 너무 복잡하고 시간을 들여도 잘 되지 않았기에 깔끔하게 포기했다 🤓ㅎ,,
todoCompleted
in CSS.todoCompleted { font-style: italic; color: lightgray; opacity: .2; text-decoration: line-through; }
위의 CSS 효과를 클릭 시마다 토글시켜준다.
추가 버튼도 위의 버튼들과 마찬가지로 이벤트 리스너를 추가해주고 함수를 만들어주면 된다.
addTodoBtn.addEventListener("click", addTodoBtnFunction); function addTodoBtnFunction() { printTodo( event.target.parentNode.parentNode.childNodes[1].childNodes[1].value ); todoInput.value = ""; }
console.dir(event.target);
으로input
의 경로를 찾은 후.value
메소드로 입력창에 입력되어 있는 값을printTodo
함수로 넘겨준다.todoInput.value = "";
-todoHandleSumit
에서와 마찬가지로 입력창을 비워준다.
투두 리스트는 이것으로 기능 구현이 끝이다.🙆🏻♂️
이름을 넣는 부분과 시계는 그냥 보너스로 넣어본 것이니 따로 리뷰는 하지 않겠다. (귀찮은 거 아님ㅎ)
transitionend
를 사용했음에도 불구하고 가끔 효과가 씹히는(?) 경우가 생긴다. 🤷🏻♂️ 왜 때문이지..?위코드의 사전 스터디인 2주 차 목표였던 투두리스트를 이렇게 끝내고 나니 자바스크립트도 재미있는 것 같다. 3주 차 과제인 파이썬도 재미있게 할 수 있었으면 좋겠다!
마지막으로 투두리스트짤 다시 보기 ㅋ,ㅋ,,