게으른개발너D

[JS] Momentum App 4 - To Do 본문

프로젝트/Side Project

[JS] Momentum App 4 - To Do

lazyhysong 2023. 2. 13. 14:51

Adding to dos

const toDoForm = document.querySelector("#todo-form");
const toDoInput = toDoForm.querySelector("input");
const toDoList = document.querySelector("#todo-list");

/**
 * 작성한 내용을 li 태그에 담아서 화면에 출력한다.
 * @param {*} newTodo 
 */
function paintToDo(newTodo) {
    const li = document.createElement("li");
    const span = document.createElement("span");
    li.appendChild(span);
    span.innerText = newTodo;
    toDoList.appendChild(li);
}

/**
 * 내용을 작성 후 바로 새로운 내용 작성을 위해 빈칸으로 만든다.
 * @param {*} event 
 */
function handleToDoSubmit(event) {
    event.preventDefault();
    const newTodo = toDoInput.value;
    toDoInput.value = "";
    paintToDo(newTodo); //화면에 출력
}

toDoForm.addEventListener("submit", handleToDoSubmit);

HTML 파일에 to do list를 작성할 form과 to do list를 출력할 ul 태그를 작성한다.

그리고 화면에서 input form에 작성한 내용을 출력하는 코드를 js 파일에 작성했다.

 

 

 


Deleting to dos

to do list를 생성하면서 그 리스트를 삭제할 수 있는 버튼을 만들자

function paintToDo(newTodo) {
    const li = document.createElement("li");
    const span = document.createElement("span");
    span.innerText = newTodo;
    const button = document.createElement("button");
    button.innerText = "X";
    button.addEventListener("click", deleteDoto);
    li.appendChild(span);
    li.appendChild(button);
    toDoList.appendChild(li);
}

createElement로 button을 만들고 그 버튼에 eventListener를 추가해줬다.

하지만 버튼을 클릭해 봤자. 이게 어떤 버튼을 클릭했는지 알 수 없다.

이걸 알아내기 위해 해당 버튼 클릭시 실행되는 함수에 기본적으로 제공되는 정보를 보자.

이 정보는 역시나 event라는 param에 담아서 보았다.

이런식으로 나오는데 여기서 target이라는 property를 보면 button이라고 나와있다.

target은 자기 자신을 가리키는 것이다.

target을 열어서 살펴보면 다음과 같이 나온다.

여기서 눈여겨 볼 것은 parentElement이다.

li라고 나오는데 이 li는 클릭한 button의 바로 부모인 li를 가리키는 것이다.

이걸 이용해서 button을 클릭했을 때 바로 부모 li를 통째로 제거한다.

function deleteDoto(event) {
    const li = event.target.parentElement;
    li.remove();
}

target의 parentElement를 즉, 부모인 li를 변수에 담은 후 그걸 제거해 주었다.

to do list를 여러개 적은 후 삭제 버튼을 누르면 해당하는 list를 삭제할 수 있다.

 

 

 

 

 


Saving to dos

to do list를 작성해도 페이지를 리로딩 시키면 작성한 리스트가 사라진다.

작성한 것을 저장하기 위해 LocalStorage를 이용한다.

toDos라는 Array 껍데기를 만들어서 작성한 list를 배열로 저장 후 localStorage에 저장해보자.

const toDos = [];

function handleToDoSubmit(event) {
    event.preventDefault();
    const newTodo = toDoInput.value;
    toDoInput.value = "";
    toDos.push(newTodo);
    paintToDo(newTodo); //화면에 출력
    saveToDos();
}

to do list를 화면에 출력하기 전에 newTodo를 toDos에 삽입한다.

그리고 saveTodos 함수를 호출한다.

function saveToDos() {
    localStorage.setItem("toDos", toDos);
}

saveTodos 함수에서 localStrage에 toDos 배열을 저장한다.

하지만 문제점이 있다.

localStarage는 string만 저장하지 배열을 저장하지 않는다는 점.

to do list를 이렇게 작성했을 때

Application에서 저장된 값을 보면 다음과 같이 나온다.

배열이 아닌 그냥 콤마로 구분되어 저장된다.

나중에 화면에 출력할 것을 대비하여 이걸 배열모양 그대로 저장하려면

배열 모양 그대로를 string으로 변환하고 난 후 저장해 줘야한다.

이걸 JSON.stringify로 해결할 수 있다.

JSON.stringigy(toDos)를 콘솔에 출력하면 다음과 같이 나온다.

 
function saveToDos() {
    localStorage.setItem("toDos", JSON.stringify(toDos));
}

이제 Aplication에서 localStorage에 저장된 값을 보면 다음과 같이 배열 형태로 나온다.

 

 

 

 

 

 


Loading to dos

1. forEach

일단 LocalStorage에 저장했다.

다음엔 리로딩 했을 때 리스트가 유지되도록 만들어야 한다.

위에서 페이지에 리스트와 삭제 버튼을 출력할 수 있는 함수 paintTodo(newTodo)를 만들었다.

localStorage에 저장한 리스트 값이 존재할 경우 페이지에 출력하도록 만들어보자.

const savedTodos = localStorage.getItem("toDos");

if(savedTodos) {
    savedTodos.forEach(paintTodo);
}

forEach를 이용하면 savedTodos로 받을 값을 하나하나 paintTodo함수에 인자로 넣어 작동시킬 수 있다.

paintTodo의 파라미터에 따로 값을 넣을 필요가 없는 것이다. (나 처음 알았음.. ㅠㅠ 이래서 기초가 중요하다..)

2. JSON.parse

이렇게 하면 작동할까??? 작동하지 않을 것이다!

LocalStorage에 JSON.stringify를 이용하여 배열모양의 string으로 저장하였기 때문이다.

배열이 아닌 스트링이기때문에 forEach를 하면 스트링 값 하나만 (적었던 값들 통째로) paintTodo 함수에 적용될 것이다.

이걸 배열'모양'이 아닌 '배열'로 다시 꺼내와야한다.

JSON.parse를 이용하면 된다.

const savedTodos = localStorage.getItem("toDos");

if(savedTodos) {
    const parsedTodos = JSON.parse(savedTodos);
    parsedTodos.forEach(paintTodo);
}

여기까지 하면 페이지를 리로딩 하더라도 적어놨던 리스트를 다시 불러와 화면에 자동으로 출력할 수 있다.

하지만 버그가 있다.

새로운 리스트를 작성하고 페이지를 리로딩하면

이전에 저장되었던 리스트에 새로 작성한 리스트가 덮어 씌어져

새로 작성한 것만 화면에 출력이 된다.

const toDos = [];
function saveToDos() {
    localStorage.setItem("toDos", JSON.stringify(toDos));
}
...

function handleToDoSubmit(event) {
    event.preventDefault();
    const newTodo = toDoInput.value;
    toDoInput.value = "";
    toDos.push(newTodo);
    paintToDo(newTodo); //화면에 출력
    saveToDos();
}

toDoForm.addEventListener("submit", handleToDoSubmit);

리스트를 작성하면 handleToDoSubmit함수가 호출된다.

그리고 맨 위에 선언한 비어있는 배열에 새로 작성한 리스트를 추가하게 되고

새로 만들어진 배열을 toDos로 localStorage에 저장하게 된다.

그래서 리로딩 하면 새로작성한 리스트만 화면에 출력되는 것.

따라서 const로 선언했던 비어있던 배열을 let으로 변경 후

localStorage에 값이 존재할 경우

toDos에 그 값을 담아준다.

 
let toDos = [];

...
const savedTodos = localStorage.getItem("toDos");

if(savedTodos) {
    const parsedTodos = JSON.parse(savedTodos);
    toDos = parsedTodos;
    parsedTodos.forEach(paintTodo);
}

이제 새로운 리스트를 작성해도 기존 리스트가 덮어씌워지지 않는다.

 

 

 

 

 


Deleting to dos

1. Object id 추가

이제 삭제 기능을 추가할 차례이다.

현재는 리스트 중 몇개를 삭제한 후 페이지를 리로딩하면 삭제한 것이 다시 나타난다.

삭제 버튼을 클릭했을 때 우리가 무엇을 삭제했는지 알기 위해 각각의 리스트에 id를 추가해줄 것이다.

function paintToDo(newTodo) {
    const li = document.createElement("li");
    const span = document.createElement("span");
    li.id = newTodo.id;
    span.innerText = newTodo.text;
    const button = document.createElement("button");
    button.innerText = "X";
    button.addEventListener("click", deleteDoto);
    li.appendChild(span);
    li.appendChild(button);
    toDoList.appendChild(li);
}
function handleToDoSubmit(event) {
    event.preventDefault();
    const newTodo = toDoInput.value;
    const newTodoObj = {
        text: newTodo,
        id: Date.now()
    }
    toDoInput.value = "";
    toDos.push(newTodoObj);
    paintToDo(newTodoObj); //화면에 출력
    saveToDos();
}
toDoForm.addEventListener("submit", handleToDoSubmit);

리스트를 추가하면 handleToDoSubmit 함수가 호출 되는데

리스트를 저장하기 전에 리스트text와 id를 함께 묶어

newTodoObj라는 변수에 object를 담은 후 이걸 저장하였다.

id는 현재 시간을 밀리세컨드로 표시한 Date.now()로 선언했다.

paintToDo함수에선 화면에 표시될 리스트엔 newTodo.text를,

li엔 id로 newTodo.id를 element로 넣어줬다.

2. filter를 이용한 삭제

Array에 있는 엘리먼트들 중 한 id를 선택하는건 filter를 이용해서 삭제할 수 있다.

Array.filter(function);

forEach처럼 Array 뒤 에 filter(function)을 적으면

array의 엘리먼트들을 하나하나 function의 인자에 대입해서 함수를 실행시킨다.

filter는 function에서 정의한 코드들에 대해 true or false 값을 받아

true에 해당하는 엘리먼트들만 남기고 array를 다시 리턴해 준다.

스트링으로 이루어진 arr이라는 배열을 만들었다.

sexyFilter에는 "lololo"라는 스트링이 아니면 true를, 맞으면 false를 리턴하도록 만들었다.

arr에 filter를 씌우면 "lololo"를 제외한 true인 string 값만 결과값으로 나온다.

숫자로 이루어진 arr이라는 배열을 만들었다.

sexyFilter라는 함수를 만들었는데, 1000보다 작은 값들만 true로 리턴하도록 정의했다.

이제 filter를 씌우면 1000보다 작은 숫자만 결과값으로 나온다.

위의 두 예제에서 filter를 적용할 때 함수를 선언하지 않고 다음과 같은 방법으로도 쓸 수 있다.

const arr = ["lalala", "lololo", "huhuhu"];
arr.filter(str => str !== "lololo");
const arr = [1213, 123, 235, 234155512, 56, 1231, 86754, 32];
arr.filter(num => num <= 1000);

ES6에서 새로나온 함수 문법인데 함수의 이름을 새로 적을 필요없이 1회용으로 짧은 코드를 작성할 때 사용하기 좋다.

이제 filter를 to do list에 적용시켜보자.

let toDos = [];
function saveToDos() {
    localStorage.setItem("toDos", JSON.stringify(toDos));
}
...

function deleteTodo(event) {
    const li = event.target.parentElement;
    li.remove();
    toDos = toDos.filter(todo => todo.id !== li.id);
    saveToDos();
}

deleteTodo 함수 안에 filter를 적용한 toDos 배열 코드를 추가하고 그걸 다시 toDos에 담는다.

그리고 삭제되고 남은 array를 다시 저장하기 위해 saveToDos 함수를 다시 호출한다.

3. parseInt

과연 실행될까???! 시행안된다!!!!

왜냐하면 todo.id는 number이고, li.id는 string이기 때문이다.

li.id는 JSON으로 한번 변환되었기때문에 string으로 저장된다.

이걸 해결하기 위해 parseInt를 쓴다.

let toDos = [];
function saveToDos() {
    localStorage.setItem("toDos", JSON.stringify(toDos));
}
...

function deleteTodo(event) {
    const li = event.target.parentElement;
    li.remove();
    toDos = toDos.filter(todo => todo.id !== parseInt(li.id));
    saveToDos();
}

이제 삭제 기능까지 마쳤다.

페이지를 리로딩해도 삭제하거나 추가한 list는 화면에 그대로 유지가 된다.

 

 

 

 

 

 


 

 

 

todo.js

const toDoForm = document.querySelector("#todo-form");
const toDoInput = toDoForm.querySelector("input");
const toDoList = document.querySelector("#todo-list");

let toDos = [];

function saveToDos() {
    localStorage.setItem("toDos", JSON.stringify(toDos));
}

function deleteTodo(event) {
    const li = event.target.parentElement;
    li.remove();
    toDos = toDos.filter(todo => todo.id !== parseInt(li.id));
    saveToDos();
}

/**
 * 작성한 내용을 li 태그에 담아서 화면에 출력한다.
 * @param {*} newTodo 
 */
function paintToDo(newTodo) {
    const li = document.createElement("li");
    const span = document.createElement("span");
    li.id = newTodo.id;
    span.innerText = newTodo.text;
    const button = document.createElement("button");
    button.innerText = "X";
    button.addEventListener("click", deleteTodo);
    li.appendChild(span);
    li.appendChild(button);
    toDoList.appendChild(li);
}

/**
 * 내용을 작성 후 바로 새로운 내용 작성을 위해 빈칸으로 만든다.
 * @param {*} event 
 */
function handleToDoSubmit(event) {
    event.preventDefault();
    const newTodo = toDoInput.value;
    const newTodoObj = {
        text: newTodo,
        id: Date.now()
    }
    toDoInput.value = "";
    toDos.push(newTodoObj);
    paintToDo(newTodoObj); //화면에 출력
    saveToDos();
}

toDoForm.addEventListener("submit", handleToDoSubmit);

const savedTodos = localStorage.getItem("toDos");

if(savedTodos) {
    const parsedTodos = JSON.parse(savedTodos);
    toDos = parsedTodos;
    parsedTodos.forEach(paintToDo);
}

'프로젝트 > Side Project' 카테고리의 다른 글

[JS] Momentum App 6 - Style  (0) 2023.02.13
[JS] Momentum App 5 - Weather  (0) 2023.02.13
[JS] Momentum App 3 - Quotes and Background  (0) 2023.02.13
[JS] Momentum App 2 - Clock  (0) 2023.02.12
[JS] Momentum App 1 - Login  (0) 2023.02.12
Comments