[ 과제2 ] 자바스크립트를 이용해서 Todo 앱 만들기

김민지·2024년 9월 12일
0
post-thumbnail

과제 설명

요구사항

  1. 변수명을 최대한 명확히 작성, 함수도 최소한의 단위로 나누어서 사용
  2. 페이지를 새로고침해도 데이터가 지속될 수 있도록 하기
    localStorage 사용

제공 예시 이미지

HTML

  • 아이콘 생성하기
<head>
      <link
      href="https://fonts.googleapis.com/icon?family=Material+Icons"
      rel="stylesheet"
    />
</head>

JS

  1. addEventListener vs onclick
  • addEventListener는 동일한 이벤트에 대해 여러 개의 리스너를 추가할 수 있어서 같은 버튼에 여러 개의 클릭 이벤트를 설정할 수 있는 반면, onclick은 해당 이벤트에 대해 한 개의 함수만 설정할 수 있어서 새로운 함수를 할당하면 이전의 함수는 덮어쓰여짐
    ↪ 유연성을 위해 addEventListener가 더 권장되는 방식임
  • 그러나 함수가 하나일 때는 다음 두 표현은 같은 결과를 가져옴
    버튼.addEventListener('click', 함수) == 버튼.onclick=function(){...}
  1. 브라우저의 localStorage 에 데이터 저장
// 수정하고자 하는 요소들 불러오기: 
// todo 박스들이 나열되어서 들어갈 div
const list = document.getElementById("list");
// header에 있는 새로운 TODO 추가하기 버튼
const createBtn = document.getElementById("create-btn");

// todo 객체의 내용 저장용 리스트
// 예: let todos = [{id: ceioqhqqw, text: '과제 완성하기', complete: true}]; 
let todos = [];

// `새로운 TODO 추가하기 버튼`을 클릭하면 새로운 아이템 객체가 생성되도록 함
createBtn.addEventListener('click', createNewTodo);

function createNewTodo() {
	// 새로운 todo 객체 생성
	const item = {
		id: new Date().getTime(), // unique한 id를 만들기 위해 현재 날짜 + 시간 사용
		text: "", // input field에 입력될 text
		complete: false // checkbox의 checked 여부
	}

	// unshift: 배열의 맨 처음에 새로운 todo 객체를 추가
	// * push: 배열의 맨 끝에 새로운 요소를 추가하므로 새로운 todo를 위쪽에 생성해야 하는 상황에서는 부적합
	// * shift: 배열의 맨 첫번째 요소를 제거하는 메소드
	todos.unshift(item);

	// 여기까지는 todo 객체의 데이터를 생성하고 배열에 저장함
	// 아래부터는 생성된 todo 객체가 화면에 보이도록, 요소를 생성해야 함
	// 이 때, 요소를 생성하는 작업도 큰 작업이므로 createTodoElement 함수 하나로 분리해서 수행

	// 함수의 return값을 두 변수로 받아오기
	const { itemEl, inputEl } = createTodoElement(item);

	// 리스트 요소 안에 방금 생성한 아이템 요소 추가(가장 첫번째 요소로 추가)
	// * append() : 맨 뒤에 추가
	list.prepend(itemEl);

	// disabled 속성 제거 + input 요소에 focus 
	// ▷ div가 생성되자마자 input field에 타이핑 할 수 있도록!
	inputEl.removeAttribute("disabled");
	inputEl.focus();

	saveToLocalStorage(); // 📌 LocalStorage에 저장 (1) 새로운 Todo 객체 생성 후!
}

/* <div class="item">
	<input type="checkbox" />
	<input 
		type="text" 
		value="Todo content goes here" 
		disabled />
	<div class="actions">
		<button class="material-icons">edit</button>
		<button class="material-icons remove-btn">remove_circle</button>
	</div>
</div> */

// 위의 html에 해당하는 새로운 todo 요소를 화면에 보이도록 생성하는 함수 작성
// 요소를 수정하는 것이 아니라 생성해야 하므로 createElement 사용
function createTodoElement(item) {
	// todo 요소를 전체 감싸는 div 생성
	const itemEl = document.createElement("div"); // div 요소 생성
	itemEl.classList.add("item"); // 태그로 class='item' 설정

	// todo div 내의 checkbox 생성
	const checkbox = document.createElement("input"); // input 요소 생성
	checkbox.type = "checkbox"; // input 요소의 type 설정
	checkbox.checked = item.complete; // checkbox의 초기상태 설정

	// checkbox가 checked로 바뀌면 해당 todo의 div 요소의 class='item complete'로 수정됨
	// 페이지가 새로고침될 때 체크되었던 체크박스들이 그대로 유지되도록 checked 여부를 저장하기 위해 class명을 변경함
	if (item.complete) {
		itemEl.classList.add("complete");
	}
	// todo div 내의 input field 생성
	const inputEl = document.createElement("input"); // input 요소 생성
	inputEl.type = "text"; // input 요소의 type 설정
	inputEl.value = item.text; // input 요소의 값 지정
	// input 요소의 속성으로 disabled를 설정하면 클릭해도 input field에 커서가 나타나지 않고 타이핑이 불가해짐
	// 지정요소.setAttribute(속성명, 속성값): 지정요소(여기서는 input요소)에 속성명(disabled)=속성값("")을 추가해줌
	inputEl.setAttribute("disabled", ""); 

	// edit, remove 버튼을 감싸는 div 요소
	const actionsEl = document.createElement("div"); // div 요소 생성
	actionsEl.classList.add("actions"); // class="actions" 설정

	// edit 버튼 생성
	const editBtnEl = document.createElement("button"); // button 요소 생성
	editBtnEl.classList.add("material-icons"); // class명 설정
	editBtnEl.innerText = "edit"; // 버튼이름 설정

	// remove 버튼 생성
	const removeBtnEl = document.createElement("button"); // button 요소 생성
	removeBtnEl.classList.add("material-icons", "remove-btn"); // class명 설정
	removeBtnEl.innerText = "remove_circle"; // 버튼 이름 설정

	// append: 각 요소들을 자신을 감싸는 div 요소 안으로 넣어주기
	// actionsEl이 editBtnEl, removeBtnEl를 감싸고 있으므로 넣어주기
	actionsEl.append(editBtnEl); 
	actionsEl.append(removeBtnEl);
	// itemEl는 모든 요소를 감싸는 가장 바깥 div이므로 전부 넣어주기
	itemEl.append(checkbox);
	itemEl.append(inputEl);
	itemEl.append(actionsEl);

	// -- EVENTS --
	// checkbox를 클릭해서 checked되면 change 이벤트가 발생함. 이 때, 
	checkbox.addEventListener("change", () => {
		// 1. item 객체에 저장된 complete값 업데이트
		item.complete = checkbox.checked;
		// 2. 가장 바깥의 todo div의 아이디값 업데이트
		if (item.complete) { 
			itemEl.classList.add("complete"); // class='item complete'로 바뀜
		} else { 
			itemEl.classList.remove("complete"); // class='item'로 바뀜
		}

		saveToLocalStorage(); 
	});

	// input field에서 input 이벤트가 발생했을 때,
	// item객체에 저장된 text값으로 input field에 input된 text값(inputEl.value)이 저장됨
	inputEl.addEventListener("input", () => {
		item.text = inputEl.value;
	});

	// item 객체가 focus 되지 않았을 때 (=blur) disabled 속성이 생기는 점 구현
	inputEl.addEventListener("blur", () => {
		inputEl.setAttribute("disabled", "");
		saveToLocalStorage();
	});

	// edit 버튼 누르면 disabled 속성 없애주고 focus되어야 함
	editBtnEl.addEventListener("click", () => {
		inputEl.removeAttribute("disabled");
		inputEl.focus();
	});

	// remove 버튼 누르면 전체 div가 삭제되어야 함
	removeBtnEl.addEventListener("click", () => {
		// 데이터 먼저 삭제
		// filter(): 배열을 순회하며 t.id == item.id인 것들은 필터링해버리고 (삭제하고) 나머지(t.id != item.id)만 반환
		todos = todos.filter(t => t.id != item.id);
		// 요소 삭제 (간단!)
		itemEl.remove();
		// 변경사항 저장
		saveToLocalStorage();
	});

	// 생성한 요소들 반환하기 
	return { itemEl, inputEl, editBtnEl, removeBtnEl }
}

// 새로고침 된 후 todos 배열에 저장된 데이터를 화면의 요소로 display하기 위한 반복문 함수
function displayTodos() {
	loadFromLocalStorage();

	for (let i = 0; i < todos.length; i++) { // 배열의 길이만큼 반복해가면서 요소 다시 생성
		const item = todos[i];

		const { itemEl } = createTodoElement(item);

		list.append(itemEl);
	}
}

displayTodos();

// 데이터를 localStorage에 저장하기 위한 함수
function saveToLocalStorage() {
	// localStorage에 들어가기 위해서는 반드시 string type이어야 함. 그러나 현재는 배열타입. 
	const data = JSON.stringify(todos); // 배열(todos) → 문자열(data)

	localStorage.setItem("my_todos", data); // setItem(key, 데이터): key는 자유롭게 설정 가능
}

// 새로고침 된 후 데이터를 localStorage로부터 받아오기 위한 함수
function loadFromLocalStorage() { 
	const data = localStorage.getItem("my_todos");

	if (data) {
		todos = JSON.parse(data); // 문자열(data) → 배열(todos)
	}
}

0개의 댓글