이번주의 스터디 주제는 바닐라 JS를 이용하여 TodoList를 만드는 것이었다.
일단 완성화면을 보고, 내가 이 TodoList를 어떻게 만들었는지 정리해보고자 한다.
<!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가 수행할 예정이기 때문이다.
const todoInput = document.querySelector("input");
const todo = document.querySelector(".todo-box");
const submitBtn = document.getElementById("submitbtn");
먼저, querySelector
와 getElementById
로 Dom 상에 있는 요소들을 가지고 오게 시켰다!
두 문법이 작성하는 데 약간 차이가 있는데..
querySelector
는 하나 이상의 선택자를 포함한 DOMString을 반환합니다. 유효한 CSS 선택자여야만 하고, 아닐 경우 예외가 발생하고, Null을 반환합니다.
참고로, 선택자가 ID 선택자인데, 해당 ID를 잘못 사용하여 문서에서 여러 번 사용하면, 그 ID를 사용한 첫 번째 요소를 반환합니다!
사용 예시를 아래에서 함께 보자.
<div id="foobar"></div>
을 불러오고 싶다면?
document.querySelector('#foobar')
만약 클래스를 불러온다면 아래와 같이 작성한다.
var el = document.querySelector(".myclass");
만약에 내가 querySelectorAll
을 사용하였다면 CSS 선택자에 대응하는 것을 모두모두 반환한당!
요소에 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
라는 리스트를 만들어 내가 새롭게 입력하게 될 리스트를 저장할 수 있도록 만들어 두었다.
지정한 유형의 이벤트 대상이 이벤트를 수신할 때마다 호출할 함수를 지정합니다! 이벤트를 등록하는 가장 권장되는 방식이라고 한다.
이것의 장점은, 하나의 이벤트 대상에 복수의 동일 이벤트 타입 리스너를 등록할 수 있다는 점이다.
<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
함수가 실행될 수 있도록 연결하여 두었다.
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
라는 변수에 넣어서 사용하는 것이다.
필요한 것을 쏙쏙 뽑아서 다음 함수에 넘겨준다.
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
태그는 고유의 동작이 존재한다.
나의 페이지 같은 경우, 버튼을 누르면 새로고침을 자동으로 수행하기 때문에 e.preventDefault()
를 넣어준다.
만약, input 값에 아무것도 넣지 않았다면 alert를 생성하여 아무것도 쓰지 않았다는 것을 사용자에게 안내한다.
input 값에 원하는 대로 값을 작성하면 printTodo
를 통하여, 화면에 나타내고 storeTodo
를 통하여 저장을 진행한다. 0
으로 같이 넘겨준 값은 수행하였는지 안하였는지를 체크하는 checked
값이다.
저장을 잘 햇으니까 input값은 빈칸으로 만들어준다.
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에 저장하는 것이다.
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
를 이용하여 문자열로 변환하여 저장합니다!
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에서 봐요~