clear_uncertainty

자바스크립트 입문[Javascript] - 자바스크립트로 투두리스트 만들기 - parentElement / target / delete / filter / forEach / JSON 본문

언어/자바스크립트(Javascript)

자바스크립트 입문[Javascript] - 자바스크립트로 투두리스트 만들기 - parentElement / target / delete / filter / forEach / JSON

SOidentitiy 2021. 7. 29. 17:41
728x90

2021-07-29

자바스크립트 학습일지입니다.

해당 내용은 노마드코더님의 <바닐라JS로 크롬 앱 만들기> 강의를 들으며 추가적인 학습을 정리한 내용입니다.


 

 

이번 포스팅에선 투두리스트(todo-list)를 만들어봅시다.

 

먼저, todo.js 라는 자바스크립트 파일을 생성한 뒤 HTML에 import 해줍시다.

todolist를 만들기위해 html에 form과 ul을 생성해줍니다.

li는 만들지않습니다. 이제 JS로 li를 만들어 html에 추가할 수 있습니다.

 

 

JS로 html에서 만든 Form과 Input과 List를 갖고옵시다.

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

 

toDoForm.addEventListener("submit", handleToDoSubmit);

저번 포스팅에서 다룬 addEventListener 메서드를 이용해 사용자가 submit할 때, handleToDosubmit 함수를 실행시킵시다.

 

우리가 원하는 것은, submit이 user가 행했을 때, input 창이 초기화되고, 사용자가 입력한 값이 저장되어 todoList에 표현되는 것입니다.

 

function handleToDoSubmit(event) {
	event.preventDefault();
	const newTodo = toDoInput.value;
  }

따라서 위와같이,  submit의 Default값인 새로고침을 preventDefault로 막아주고, 사용자가 입력한 값을 저장할 수 있도록 새로운 변수를 지정합니다.

 

function handleToDoSubmit(event) {
	event.preventDefault();
	const newTodo = toDoInput.value;
	toDoInput.value = "";
	console.log(newTodo);
}

input창을 초기화 할 수 있도록 위와같이 todoInput.value 값을 none로 설정해주고,

초기화해도 newToDo 값이 잘 저장했는지 확인하기 위해 console.log를 통해 확인할 수 있습니다.

 

이제 newToDo 값이 HTML의 Ul의 요소로 포함될 수 있도록 구현해봅시다.

 

function handleToDoSubmit(event) {
	event.preventDefault();
	const newToDo = toDoInput.value;
	toDoInput.value = "";
	paintToDo(newToDo);
}

위와 같이 paintToDo(newToDo)를 통해 저장된 newToDo를 paintToDo로 보내줍니다.

 

function paintToDo(newToDo) {
	const li = document.createElement("li") ;
	const span = document.createElement("span");
	li.appendChild(span);
	span.innerText = newToDo;
	toDoList.appendChild(li);	
}

위와 같이, paintToDo에서는 배경화면을 추가하는 저번 포스팅에서 배웠던 것처럼, creatElement를 통해

li와 span을 만들어줍시다. (나중에, 삭제하는 버튼을 만들기 위해 span에 text를 넣고 li안에 span을 넣어줍시다.)

그리고 appendChild를 통해 li 안에 span 이 들어갈 수 있도록하고, innerText를 통해 span에 newToDo의 내용이 들어갑니다.

마지막으로 appendChild를 통해 toDoList의 자식으로 지정한 li가 포함됩니다.

 

이를 통해 브라우저에서 input에 내용을 submit하면 todo-list가 구현됩니다.

옆의 HTML을 보면 ul 의 자식요소로 li 이, li의 자식요소로 span이 포함된 것을 확인할 수 있습니다.

 

이제 문제가 2개가 남았습니다.

 

하나는,todo-list를 삭제할 수 없다는 것이고, 둘째는 todo-list가 새로고침하면 없어진다는 것입니다.

 

첫번째 문제를 해결해봅시다.

 

function paintToDo(newToDo) {
	const li = document.createElement("li") ;
	const span = document.createElement("span");
	span.innerText = newToDo;
	const button = document.createElement("button");
	button.innerText = "❌";
	button.addEventListener("click", deleteToDo);
	li.appendChild(span);
	li.appendChild(button);
	toDoList.appendChild(li);	
}

먼저 버튼을 만들어줍시다. 앞서 했던 것처럼 createElemennt를 통해 button을 만들어줍시다.

button에는 ❌을 innerText로 넣어줘 삭제버튼이라는 것을 직관적으로 알수있게 해줍시다.

 

또한, button도 li에 포함시켜줍시다.

button이 클릭되는 것을 감지하기위해 , addEventListener 메서드에 click을 감지하고 deleteToDo의 함수를 실행시켜줍시다.

function deleteToDo(event) {
	const deleteToDoList = event.target.parentElement;
	deleteToDoList.remove();
}

deleteToDo(event)에서 우리는 toDoList의 어떤 button이 삭제되는지를 알고, 그 button에 해당하는 li를 삭제시켜줘야합니다.

따라서 event.target.parentElement를 통해 어떤 li인지 deleteToDoList로 설정해줍니다.

 

event. target

 

event.target은 이벤트가 발생한 요소를 반환해줍니다.

 

const myTarget = event.target;

이라는 변수가 있을 때, myTarget은 현재 이벤트가 발생한 요소를 가리킵니다. 만약 이 요소의 색상을 변경하고 싶다면 myTarget.style.color = "red"; 와 같은 DOM 메소드를 사용해야 합니다. 

 


parentElement  or  parentNode

 

target의 property중 parentElement를 사용해 어떤 li 가 선택되었는지 알 수 있습니다.

parentElement와 parentNode는 같은 기능을 하지만, 리턴값에서 약간 다릅니다.

parentElement는 부모 노드가 없을 때 null 을 리턴하지만,

parentNode는 Document node를 리턴합니다.

document.body.parentNode; // Returns the <html> element
document.body.parentElement; // Returns the <html> element

document.documentElement.parentNode; // Returns the Document node
document.documentElement.parentElement; // Returns null (<html> does not have a parent ELEMENT node)

 

따라서 deleteToDoList 는 유저가 삭제버튼을 누른 li 입니다.

.remove()를 통해  li를 삭제시킬 수 있습니다.

 

 

두번째 문제, 새로고침해도 없어지지않도록 만들어봅시다.

 

이는 username을 저장할 때와 같이, localStorage를 이용해 해결할 수 있습니다.

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

const toDos= [];

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


function deleteToDo(event) {
	const deleteToDoList = event.target.parentElement;
	deleteToDoList.remove();
}


function paintToDo(newToDo) {
	const li = document.createElement("li") ;
	const span = document.createElement("span");
	span.innerText = newToDo;
	const button = document.createElement("button");
	button.innerText = "❌";
	button.addEventListener("click", deleteToDo);
	li.appendChild(span);
	li.appendChild(button);
	toDoList.appendChild(li);	
}



function handleToDoSubmit(event) {
	event.preventDefault();
	const newToDo = toDoInput.value;
	toDoInput.value = "";
	toDos.push(newToDo);
	paintToDo(newToDo);
	saveToDos();
}



toDoForm.addEventListener("submit", handleToDoSubmit);

먼저, toDos라는 array를 만들어줍시다.

함수 handleToDosubmit(event)에 toDos.push(newToDo)를 코드해, user가 추가한 newToDo를 toDos array에 push해줍니다.

localStorage는 배열을 받을 수 없고, 문자열만 받기때문에 saveToDos 함수에서 문자열로 넣어줍니다. 

↑ 이때, 새로고침을 해도 localStorage에 사라지지않고 저장되어있는 것을 확인할 수 있습니다.

 

 

JSON.stringify( )

JSON.stringify() 메서드는 Javascript 값이나 객체를 JSON 문자열로 변환합니다. 

 

JSON.stringify({});                  // '{}'
JSON.stringify(true);                // 'true'
JSON.stringify('foo');               // '"foo"'
JSON.stringify([1, 'false', false]); // '[1,"false",false]'
JSON.stringify({ x: 5 });            // '{"x":5}'

JSON.stringify(new Date(2006, 0, 2, 15, 4, 5))
// '"2006-01-02T15:04:05.000Z"'

JSON.stringify({ x: 5, y: 6 });
// '{"x":5,"y":6}' or '{"y":6,"x":5}'
JSON.stringify([new Number(1), new String('false'), new Boolean(false)]);
// '[1,"false",false]'

 

위의 toDos를 JSON.stringify를 적용해봅시다.

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

 

이와 같이 toDos의 value는 ["a", "b", "c"]로 저장할 수 있습니다.

 

JSON.parse( )

JSON.parse() 메서드는 JSON 문자열의 구문을 분석하고, 그 결과에서 JavaScript 값이나 객체를 생성합니다. 

 

const json = '{"result":true, "count":42}';
const obj = JSON.parse(json);

console.log(obj.count);
// expected output: 42

console.log(obj.result);
// expected output: true

 

Array.prototype.forEach(function)

forEach() 메서드는 각 array 요소를 item으로 받고, 주어진 함수를 각 아이템마다 실행시킵니다.

const array1 = ['a', 'b', 'c'];

array1.forEach(item => console.log("this turn of" , item));

// expected output: "this turn of a"
// expected output: "this turn of b"
// expected output: "this turn of c"

 

JSON.parse(toDos)를 적용하면 toDos는 살아있는 array가 됩니다. 

const savedToDos = localStorage.getItem(TODOS_KEY);

(TODOS_KEY 는 "toDos"입니다.)

 

if(savedToDos !== null) { 
	const parsedToDos = JSON.parse(savedToDos);
	parsedToDos.forEach( item => console.log("this turn of " , item));
}

toDos의 localStorage에 데이터가 있다면( = savedToDos !== null) savedToDos를 JSON.parse 메서드를 통해

살아있는 배열로 만들어줍니다. 이를 parsedToDos의 함수로 정의합니다.

parsedToDos.forEach( item => console.log("this turn of " , item) ) 를 통해 

parsedToDos의 각 아이템을 console 창에 "this turn of  item" 꼴로 나타납니다.

 

<지금까지 정리하자면 JavaScript의 object 나 array 등을 string으로 변환시킬 수 있습니다.(JSON.stringify 메서드)>

<그 string을 가지고 string이 아닌 JavaScript에서 사용 가능한 object로 만들 수 있습니다. (JSON.parse 메서드)>

<그 다음에는 array의 각 item에 대해 하나의 function을 실행시킬 수 있습니다. (forEach 메서드)>

 

우리는 console.log ("this turn of", item)이 필요하지않습니다.

(console.log 예시는 이해를 위한 도구로 많이 사용됩니다.)

 

우리가 원하는 건, parsedToDos  array 내부의 item 들을 가지고 item을 브라우저에 나타내는 것입니다.

브라우저에 todo-list를 나타내는 함수는 위의 paintToDo를 사용합니다. 

 

그러나 이때 새로고침을 하고 새로 추가할 때 문제가 생깁니다.

↑a, b, c를 입력하고 새로고침 했을 때의 모습 ( localStorage와 브라우저에 나타나는 것을 확인할 수 있습니다.)

 

↑d, e, f을 추가로 입력했을 때, localStorage에서 toDos의 값이 바뀌고, 예전의 a, b, c의 값들이 사라졌습니다.

 

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

const TODOS_KEY = "toDos";

let toDos= [];

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


function deleteToDo(event) {
	const deleteToDoList = event.target.parentElement;
	deleteToDoList.remove();
}


function paintToDo(newToDo) {
	const li = document.createElement("li") ;
	const span = document.createElement("span");
	span.innerText = newToDo;
	const button = document.createElement("button");
	button.innerText = "❌";
	button.addEventListener("click", deleteToDo);
	li.appendChild(span);
	li.appendChild(button);
	toDoList.appendChild(li);	
}



function handleToDoSubmit(event) {
	event.preventDefault();
	const newToDo = toDoInput.value;
	toDoInput.value = "";
	toDos.push(newToDo);
	paintToDo(newToDo);
	saveToDos();
}

const savedToDos = localStorage.getItem(TODOS_KEY);

if(savedToDos !== null) { 
	const parsedToDos = JSON.parse(savedToDos);
	toDos = parsedToDos;
	parsedToDos.forEach( item => paintToDo(item));
}


toDoForm.addEventListener("submit", handleToDoSubmit);

 

지금까지 작성한 코드를 확인해봅시다. const toDos = []로 정의된 것을 let toDos = []로 바꿔줍시다.

toDos는 값이 저장되어있어야되기때문에 업데이트가 가능해야합니다. 따라서 const 가 아닌 let로 바꿔줍니다.

toDos는 코드 상에서 빈 array이고, handleToDoSubmitd은 비어있는 toDos에 newToDo를 push 합니다.

따라서 원래의 toDos는 사라지고 newToDo만 덮어씌어지게 됩니다.

if(savedToDos !== null) 문에서 toDos = parsedToDos 를 작성해 toDos를 빈 array가 아닌 이전의 newToDo를 저장한 채 parsedToDos.forEach ( item => paintToDo(item) ) 를  실행시킵니다.

위 코드상의 paintToDo(newItem) 에서 newItem은 item으로 대체되고 이는, 이전의 newToDo입니다.  

이 과정을 걸쳐, 이제 새로고침한 후 todo-list를 추가시키고 다시 새로고침할 때,

처음의 값들과 이전의 값들이 모두 브라우저에 나타납니다.

 

하지만, 이번엔 todo-list를 삭제시킨 후 새로고침할 때, 삭제가 적용되지않은 채 브라우저에 나타나는 문제가 나타납니다. 

유저가 TODOLIST를 적을때 localStorage는 todolist만 저장됩니다.

유저가 삭제버튼을 눌렀을때 어떤 list를 삭제했는지 알 수 있어야합니다. 그리고 이것이 HTML에도 유효해야합니다.

 

따라서 list 요소에 id를 부여해봅시다.

function handleToDoSubmit(event) {
	event.preventDefault();
	const newToDo = toDoInput.value;
	toDoInput.value = "";
	const newToDoObject = {
		text: newToDo,
		id: Date.now(),
	};
	toDos.push(newToDoObject);
	paintToDo(newToDoObject);
	saveToDos();
}

text는 newToDo를 그대로 받고 id를 Date.now() 로 받아줍니다. Date.now 메서드는 1970년 1월 1일 0시 0분 0초부터 현재까지 경과된 밀리 초를 반환합니다. 이 값은 중복되지않기에 id로 지정하여도 겹치지않습니다.

id를 부여해준 newToDoObeject를 toDos 배열에 push해주고, paintToDo 함수의 매개변수로 적용합니다.

function paintToDo(newToDo) {
	const li = document.createElement("li") ;
	li.id = newToDo.id;
	const span = document.createElement("span");
	span.innerText = newToDo.text;
	const button = document.createElement("button");
	button.innerText = "❌";
	button.addEventListener("click", deleteToDo);
	li.appendChild(span);
	li.appendChild(button);
	toDoList.appendChild(li);	
}

paintToDo에서 li의 id를 newToDo의 id로 지정해줍니다.

 

이로써 HTML에 각 리스트에 해당하는 중복되지 않은 id가 지정됩니다.

↑HTML의 li에 id가 지정된 모습

 

지정된 id를 통해 어떻게 삭제버튼을 누를때 , array에서 제외시킬 수 있는지 알아봅시다.

이를 해결하기 위해, filter( ) 메서드가 사용됩니다.

 

Array.prototype.filter(function)

 

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

테스트를 통과하지 못하면 빈 배열을 반환합니다.

각 요소의 return이 true면 요소를 유지하고 false를 반환하면 버립니다.

 

위를 다른 방식으로 작성하면 아래와 같습니다. 아래는 function을 선언할 필요가 없어집니다.

 

이를 이용해 이제 삭제버튼으로 해당하는 li를 삭제하고, 이를 새로고침을 해도 유지시켜줍시다.

 

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

 

deleteToDo를 다음과 같이 바꿔줍시다.

toDos가 배열이므로, toDos의 아이디가 위의 삭제가 되야하는 li의 아이디가 같지않을때 true를 return하도록 하고

이를 filter 해주면, 삭제가 되야하는 li 만 false를 return하고 나머지는 true하기 때문에

우리가 원하는 array를 만들 수 있습니다.

그리고 이를 saveToDos 함수를 통해 localStorage 시켜줘야 새로고침을 해도 변하지않을 수 있습니다.

 

지금까지의 포스팅 중 가장 길고 긴 투두리스트가 끝이 났습니다.

이해가 안될땐 찾아보고, 코딩을 직접 해보는 것도 좋지만, 위 일련의 글을 읽으며 이해하는 과정도 해줍시다.

 


 

출처

 

 

[javascript] 부모 엘리먼트(노드)

parentElement or parentNode 특정 엘리먼트의 부모 엘리먼트를 가지고 있는 속성값이다. 이 속성값은 엘리먼트 마다 할당되어 있다. parentElement와 parentNode는 같은 기능을 하지만, 리턴값에서 약간 다르

gafani.tistory.com

 

 

[javascript / 자바스크립트] 이벤트가 발생한 대상(요소) 얻기 - event.target

event.target event.target은 이벤트가 발생한 요소를 반환해준다. 예를 들어 $("a").click(function(event){ console.log(event.target); }); 이라는 코드는 a요소를 클릭했을 때 이벤트가 발생하고, 이벤트가 발..

mjmjmj98.tistory.com

 

 

Event.target - Web API | MDN

Event interface의 target 속성은  event가 전달한 객체에 대한 참조입니다. 이는 이벤트의 버블링 또는 캡처 단계에서 이벤트 핸들러를 호출하는 Event.currentTarget (en-US)와 다릅니다.

developer.mozilla.org

 

 

 

JSON.stringify() - JavaScript | MDN

JSON.stringify() 메서드는 JavaScript 값이나 객체를 JSON 문자열로 변환합니다.

developer.mozilla.org

 

 

 

Array.prototype.forEach() - JavaScript | MDN

forEach() 메서드는 주어진 함수를 배열 요소 각각에 대해 실행합니다.

developer.mozilla.org

 

 

Array.prototype.filter() - JavaScript | MDN

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

developer.mozilla.org

 

 

노마드 코더 Nomad Coders

코딩은 진짜를 만들어보는거야!. 실제 구현되어 있는 서비스를 한땀 한땀 따라 만들면서 코딩을 배우세요!

nomadcoders.co

 

 

728x90