[JavaScript] 바닐라 JS로 TodoList 만들기!

김유진·2022년 10월 3일
4

Javascript

목록 보기
14/22

이번주의 스터디 주제는 바닐라 JS를 이용하여 TodoList를 만드는 것이었다.
일단 완성화면을 보고, 내가 이 TodoList를 어떻게 만들었는지 정리해보고자 한다.

1. html로 뼈대 만들기

<!DOCTYPE html>
<html lang="ko">
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<title>Eugene's Todo Mate</title>
		<link rel = "stylesheet" href = "todo.css"/>
	</head>
	<body>
		<div class = "container">
			<div class = "Todotitle">
				<h1>Eugene Todo Mate</h1>
			</div>
			<div class = "subtitle">
				갓생을 살자 🤗
			</div>
			<div class = "appendbox">
				<form id = "todo-form">
					<input id="whattodo" type = "text" placeholder="할 일을 작성하세요">
					<input id = "submitbtn" type = "submit" value = "추가하기!">
				</form>
			</div>
				<ul class ="todo-box"></ul>
			</div>
		</div>
	</body>
		<script src = "script.js"></script>
</html>

뼈대는 이렇게 간단하게 만들었다. 대부분의 기능을 JavaScript가 수행할 예정이기 때문이다.

2. Js로 기능 설정하기

① html에 설정한 요소 변수에 저장해두기(사전작업)

const todoInput = document.querySelector("input");
const todo = document.querySelector(".todo-box");
const submitBtn = document.getElementById("submitbtn");

먼저, querySelectorgetElementById로 Dom 상에 있는 요소들을 가지고 오게 시켰다!
두 문법이 작성하는 데 약간 차이가 있는데..

ⓐ querySelector

querySelector는 하나 이상의 선택자를 포함한 DOMString을 반환합니다. 유효한 CSS 선택자여야만 하고, 아닐 경우 예외가 발생하고, Null을 반환합니다.

참고로, 선택자가 ID 선택자인데, 해당 ID를 잘못 사용하여 문서에서 여러 번 사용하면, 그 ID를 사용한 첫 번째 요소를 반환합니다!
사용 예시를 아래에서 함께 보자.

<div id="foobar"></div>

을 불러오고 싶다면?

  document.querySelector('#foobar') 

만약 클래스를 불러온다면 아래와 같이 작성한다.

var el = document.querySelector(".myclass");

만약에 내가 querySelectorAll을 사용하였다면 CSS 선택자에 대응하는 것을 모두모두 반환한당!

ⓑ getElementById

요소에 id 속성이 있으면 getElementById를 통하여 접근할 수 있다. 이 때 id는 중복되면 안된다는 것에 주의하기~

<div id="elem">
  <div id="elem-content">Element</div>
</div>

여기서 자바스크립트 스크립트를 뽑아내려면~

let elem = document.getElementById('elem');

이렇게 뽑아내야 한다!

일단은 이렇게 DOM 요소를 뽑아내는 것을 통하여 html 코드 상에 표현해두었던 input 박스와, 할 일이 들어가게 될, todo-box와 , 제출 버튼을 변수에 저장해두었다.

② 저장공간 만들기, 제출버튼 기능설정하기

let todoList = [];

function setting() {
    loadStorage(); 
    submitBtn.addEventListener("click", createList);
}
setting();

todoList라는 리스트를 만들어 내가 새롭게 입력하게 될 리스트를 저장할 수 있도록 만들어 두었다.

addEventListender

지정한 유형의 이벤트 대상이 이벤트를 수신할 때마다 호출할 함수를 지정합니다! 이벤트를 등록하는 가장 권장되는 방식이라고 한다.
이것의 장점은, 하나의 이벤트 대상에 복수의 동일 이벤트 타입 리스너를 등록할 수 있다는 점이다.

<input type="button" id="target" value="button" />
<script>
    var t = document.getElementById('target');
    t.addEventListener('click', function(event){
        alert(1);
    });
    t.addEventListener('click', function(event){
        alert(2);
    });
</script>

이를 통하여 submit 버튼에 createList 함수가 실행될 수 있도록 연결하여 두었다.

② localStorage 로드하기

function loadStorage() 
{
    const storedTodo = localStorage.getItem("TODO");
    if(storedTodo != null)
    {
        const myTodoList = JSON.parse(storedTodo);
        myTodoList.forEach( todo => {
            const { text } = todo;
            const { checked } = todo;
            printTodo(text, checked);
            storeTodo(text, checked);
        });
    }   
}

localStorage 저장한것을 불러오기 위하여, storedTodo라는 변수를 하나 만들었다.

여기 보면 TODO라는 키에, 배열에 저장되어야 하는 값들이 나열되어 있다.
getitem 키를 통하여 localStorage 값 안에 있는 배열을 가지고 온다.

단, 가져온 것이 스트링 형식으로 되어 있기 때문에 스트링 형식을 다시 객체로 바꾸어주는 작업을 진행합니다.

그리고 forEach 함수를 통하여 객체의 값을 하나씩 접근한다. const { text } = todo; 는 배열 중에서 text 에 속하는 것을 가져와서 text라는 변수에 넣어서 사용하는 것이다.
필요한 것을 쏙쏙 뽑아서 다음 함수에 넘겨준다.

③ 작성한 todo리스트 배열 만들기!

function createList(e)
{
    e.preventDefault();
    const todoValue = todoInput.value;
    if(todoValue =="")
        alert("할 일을 입력해주세요.")
    else
    {
        printTodo(todoValue, 0);
        storeTodo(todoValue, 0);
        todoInput.value = "";
    }
}

createList함수는 내가 직접 입력한 todo값이 저장되기 위하여 사용된다.
일단 SubmitBtn에서 온 이벤트이니까, 이에 대해서 어떻게 click이벤트를 수행할 것인지 결정해보자.
html에서 submit이나 a 태그는 고유의 동작이 존재한다.

  • 페이지를 이동시킨다. (새로고침을 시킬 수 있음!0
  • form 안에 있는 input을 전송한다.

나의 페이지 같은 경우, 버튼을 누르면 새로고침을 자동으로 수행하기 때문에 e.preventDefault()를 넣어준다.

만약, input 값에 아무것도 넣지 않았다면 alert를 생성하여 아무것도 쓰지 않았다는 것을 사용자에게 안내한다.
input 값에 원하는 대로 값을 작성하면 printTodo를 통하여, 화면에 나타내고 storeTodo를 통하여 저장을 진행한다. 0으로 같이 넘겨준 값은 수행하였는지 안하였는지를 체크하는 checked 값이다.

저장을 잘 햇으니까 input값은 빈칸으로 만들어준다.

④ localStorage에 저장하자!


function storeTodo(todoValue, checkValue)
{
    const todoListObj = {
        text : todoValue,
        id : todoList.length + 1,
        checked : checkValue,
    };
    todoList.push(todoListObj);
    localStorage.setItem("TODO", JSON.stringify(todoList));
} 

우리는 localStorage에 object를 저장할 예정이다..그러니까 그에 관련된 object를 미리 생성해둡니다.
todoListObj로 object를 생성하고, 그에 걸맞는 value값들을 하나씩 저장해둡니다.
그리고 그 만든 객체를 배열에다가 하나씩 저장을 하고, localStorage에 저장을 합니다 그래서 setItem을 세팅해두는데, 이 때 setItem을 어떻게 하냐면 stringify를 이용한다. 자바스크립트의 값을 문자열로 만들어서 localStorage에 저장하는 것이다.

⑤ 화면에 list를 나타내보자.

function printTodo(todoValue, checkValue)  
{
    const li = document.createElement("li");
    const   span = document.createElement( "span");
    const delBtn = document.createElement("button");
    delBtn.innerText = "X";
    if (checkValue == 1)
    {
        span.InnerHTML = todoValue;
        li.appendChild(span);
        li.appendChild(delBtn);  
        li.id = todoList.length + 1;
        li.style.color = "#ccc";
        li.style.textDecoration="line-through";
        todo.appendChild(li);
    }
    else (checkValue == 0)
    {
        span.innerHTML = todoValue;
        li.appendChild(span);
        li.appendChild(delBtn); 
        li.id = todoList.length + 1;
        todo.appendChild(li);
    }
    span.addEventListener("click", checkTodo);
    delBtn.addEventListener("click", deleteTodo);
}

printTodo의 전체 모습이다.
DOM의 자식 요소를 만들어야 하기 때문에 createElement를 이용하였다. 이를 통하여 html의 요소를 내가 직접 만들어낼 수 있는 것이다!

나는 그냥 귀찮아서(..)사실 만든 html 요소에 대해서 class나 id값을 지정해주지 않았는데, 코드가 복잡해질 것을 생각하여 미리미리 달아두면 정말 좋을 거 같다.

여기서 checkValue가 하는 것은, 이 Todo가 완료된 일인지 아닌지 체크하는 용도로 쓰인다. 그래서 checkValue가 1인지, 0인지에 따라서 수행하는 영역이 다르다.

만약 checkValue가 1이라면,

li.style.color = "#ccc";
li.style.textDecoration="line-through";

이렇게 스타일을 적용해서 제외시킵니다.
그리고 만든 요소는 깜빡하지 않고 부모 요소에 연결해두기~

 todo.appendChild(li);

요롷게!

할 일의 목록을 누르면, 그것이 체크되게끔 만들고 싶으니까 몸체에 addEvenListener을 달아 주어야 하고, 리스트를 삭제하면 그 요소 또한 삭제가 되어야 하니까, addEventListenr을 달아둡니다.

span.addEventListener("click", checkTodo);
delBtn.addEventListener("click", deleteTodo);

⑥ 삭제 기능 구현하기!


function deleteTodo(e)
{
    const {target : button} = e;
    const li = button.parentNode;
    todo.removeChild(li);
    todoList = todoList.filter((todo) => todo.id != Number(li.id));
    localStorage.setItem("TODO", JSON.stringify(todoList));
}

브라우저는 EventListener을 호출할 때, 사용자로부터 어떤 이벤트가 발생하였는지에 대한 정보를 담은 이벤트 객체를 생성하여, 리스너 함수에게 전달합니다.

이벤트 객체는 타입을 나타내는 type 프로퍼티와, 이벤트의 대상을 나타내는 target 프로퍼리틑 가집니다! 이러한 이벤트 객체는 이벤트 리스너가 호출될 때 인수로 전달됩니다.

그래서 deleteTodo함수가 이벤트 객체를 매개변수로 받아옵니다.
이 때, 이벤트 객체에 담긴 특정한 button을 가져옵니다. event.target.button과 다를 바 없음!
해당 버튼이 담겨져 있는 parentNode인 li를 찾아옵니다. 그래서 그 li를 지워주고, todoList는 방금 삭제한 것을 제외시키고 새로운 리스트로 저장해줍니다. 그 과정에서 filter함수를 사용하는 것!

todoList = todoList.filter((todo) => todo.id != Number(li.id));

이렇게~
여기서 왜 Number(li.id)를 사용하나면,
DOM 구조에서 li에 관한 정보는 String형식으로 지정되어 있기 때문에 Number로 바꾸어 주는 것이다.

역시 삭제한 부분을 반영하여 저장해야겠지요. JSON.stringify를 이용하여 문자열로 변환하여 저장합니다!

⑦ 완료된 Todo는 체크표시하기!

function checkTodo(e)
{
    const {target : span} = e;
    const li =  span.parentNode;
    li.style.color = "#ccc";
    li.style.textDecoration="line-through";
    todoList.forEach( currentElement => {
        if(currentElement.id ==  Number(li.id))
            currentElement.checked = 1;
    });
    localStorage.setItem("TODO", JSON.stringify(todoList));
}

삭제 함수와 거의 다른 점이 없다. 다만 다른 점이라고 하면 forEach함수를 돌면서 checked 속성을 1로 바꾸어 준 것!


이번에 TodoList를 바닐라JS로 구현하면서 재미있기도 하였고, 역시 바닐라JS만을 사용하면 간단한 함수를 복잡하게 구현하게 될 수도 있겠다는 점을 깨달았다. 이번에 스터디원들과 함께 자신이 작성한 코드를 리뷰하는 시간을 가졌는데, 매우 재미있었고, 다음에 나도 스크롤바까지 구현해서 나의 Todo를 다듬어야겠다는 생각을 할 수 있었다. 디자인 더 예쁘게 해서 React에서 봐요~

0개의 댓글