Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1주차] 고주희 미션 제출합니다. #1

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c8d73d9
feat: 기능 넣기 전 1차 디자인 완료
ZUITOPIA Aug 25, 2024
54d1a35
feat: 기능 구현 및 주석 추가
ZUITOPIA Aug 26, 2024
03df37d
refactor: 큰 기능은 arrow func 사용하지 않고 기본 func 포맷으로 변경
ZUITOPIA Aug 27, 2024
47018a6
style : 눈누 빙그레체 폰트 적용
ZUITOPIA Aug 27, 2024
9e2a5c9
feat: input 창에 todo 입력 후 Enter키로도 add todo 되도록
ZUITOPIA Aug 27, 2024
d95c86f
fix: 한글 입력했을 때 enter 클릭 시 2번씩 적용되는 현상 해결
ZUITOPIA Aug 27, 2024
ee9bde1
style: 리스트 위 margin 조정
ZUITOPIA Aug 27, 2024
8cfea2a
refactor: 배열에 push 대신 unshift 함으로써 추가된 최신순으로 보이도록 변경
ZUITOPIA Aug 27, 2024
782d345
feat: input 창 autofocus 추가
ZUITOPIA Aug 27, 2024
c62faf4
refactor: 재할당되지 않는 값은 const 키워드로 선언하기
ZUITOPIA Aug 29, 2024
8f56746
refactor: 반복되어 사용되는 변수는 전역 변수로 사용하기
ZUITOPIA Aug 29, 2024
3bfd9c5
refactor: 아무런 값이 입력되지 않고 추가버튼 눌렀을 때 alert 창 추가
ZUITOPIA Aug 29, 2024
0ea1258
refactor: localStorage와 접근하는 함수 따로 빼내고 가독성 높이기
ZUITOPIA Aug 29, 2024
dc2afad
refactor: 재할당되지 않는 값은 const 키워드로 선언하기, JS의 불변성을 위해 filter 함수 사용하기
ZUITOPIA Aug 29, 2024
9ef7ec7
refactor:innerHTML을 사용하지 않고 DOM을 직접 조작하여 보안성 높이고 XSS(크로스 사이트 스크립팅) 공…
ZUITOPIA Aug 29, 2024
e4219ae
refactor: 엔터키로도 제출되도록 변경 - button의 type을 submit으로 변경하여 해결
ZUITOPIA Aug 29, 2024
35790d0
style: 디자인 수정
ZUITOPIA Aug 29, 2024
1c3b08d
refactor: unshift 대신 spread 연산자 적용 안된부분 적용하기
ZUITOPIA Aug 29, 2024
23b0c60
style: 박스 넘어서는 현상 수정
ZUITOPIA Aug 29, 2024
4519d52
refactor: innerHTML 제거 안 된 부분 수정
ZUITOPIA Aug 29, 2024
50e36c3
feat: Done을 다시 Todo로 돌려주는 함수도 추가
ZUITOPIA Aug 29, 2024
cb315a4
style: Done 리스트 요소들에는 line-through 스타일 주기
ZUITOPIA Aug 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added images/delete.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/emoji.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 33 additions & 10 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vanilla Todo</title>
<link rel="stylesheet" href="style.css" />
</head>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>📚 TO DO LIST</title>
<link rel="stylesheet" href="style.css" />
</head>

<body>
<div class="container"></div>
</body>
<script src="script.js"></script>
<body>
<div class="container">
<!-- <header>📚 투두리스트</header> -->
<header>
<img id="emoji" src="/images/emoji.png" alt="이모지" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미지 너무 귀여워요☺️

</header>
<form class="input-box">
<input id="todo-input" autofocus placeholder="할 일을 입력하세요" />
<button type="submit" id="add-todo-btn">➕</button>
</form>

<section class="todo-list-box">
<h4 id="todo-list-title">📋 TO DO (0)</h4>
<ul class="todo-list">
<!-- 할 일 추가될 곳 -->
</ul>
</section>
<hr />
<section class="done-list-box">
<h4 id="done-list-title">💿 DONE (0)</h4>
<ul class="done-list">
<!-- 완료된 일 추가될 곳 -->

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HTML 안에서 목록들이 추가될 곳에 주석으로 정리 해주셔서
DOM 조작할 때 항목들이 어디에 들어갈지 바로 알아볼 수 있어서 너무 좋습니다!

</ul>
</section>
</div>
<script src="/script.js"></script>
</body>
</html>
115 changes: 114 additions & 1 deletion script.js
Original file line number Diff line number Diff line change
@@ -1 +1,114 @@
// 다들 화이팅!! ٩( *˙0˙*)۶
const addTodoBtn = document.getElementById("add-todo-btn");
const todoInput = document.getElementById("todo-input");
const doneList = document.querySelector(".done-list");
const todoListTitle = document.getElementById("todo-list-title");
const todoList = document.querySelector(".todo-list");
const doneListTitle = document.getElementById("done-list-title");

// localStorage에서 값을 가져오는 함수
function getLocalStorageItem(key) {
return JSON.parse(localStorage.getItem(key)) || [];
}

// localStorage에 값을 새로 저장하는 함수
function setLocalStorageItem(key, data) {
localStorage.setItem(key, JSON.stringify(data));
}
Comment on lines +8 to +16
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

localStorage에서 값을 가져오고 저장하는 일이 빈번한데, 이를 getter와 setter 메서드로 정리해서 코드 안전성/유지보수성을 높인 부분이 좋습니다!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

처음 코드 작성 할 때부터 꼭 수정해봐야겠다고 생각했던 부분이라, 리팩토링 시 가장 우선순위로 두었던 것인데 드러난 것 같아 뿌듯합니다 🤩


function handleAddTodo() {
let inputValue = todoInput.value; // 입력한 todo 집중
if (!inputValue) return alert("내용을 입력해주세요!"); // 입력된 todo가 존재하지 않는다면 아무것도 하지 않기 위함
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

공백 문자 입력을 방지한 부분이 좋습니다👍🏻👍🏻


const todos = getLocalStorageItem("todos"); // localstorage에 저장해둔 todo를 가져와서 객체로 (아직 아무것도 없다면 빈 배열로 초기화)
todos.unshift(inputValue); // 입력된 todo를 추가
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unshift()를 사용하신 이유가 최신순으로 정렬하기 위함일까요?!
다른 분들 코드에도 코멘트 달았던 내용이긴 한데 unshift()는 원본 배열을 직접 변경하기 때문에, JavaScript로 배열을 조작할 때에는 새로운 배열을 만들고, 그 배열을 할당하는 방식으로 진행하는 것이 좋습니다!


그 이유가 궁금하시다면,,,

JS의 immutability(불변성) 관련된 문제인데 이 글에 자세히 나와 있어서 참고하시면 좋을 것 같아요!

간단히 설명드리자면, 배열의 값을 직접 변경할 경우 디버깅이 어렵고 의도치 않은 결과를 불러올 수 있기 때문에 배열의 값을 직접 변경하는 방식이 아닌 새로운 배열을 만들어 할당하는 방식을 추천합니다. 따라서 값을 직접 변경하는 push() , unshift(), splice() 등의 메서드보다 새로운 배열을 반환하는 map(), filter() 혹은 spread 연산자를 사용하는 방식을 권장합니다 👀 또한 나중에 React를 사용하실 때에도 배열 불변성을 지켜 주셔야 상태 변화 감지와 성능 최적화 부분에서 용이하답니다!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞습니다! 최신순을 위해 push에서 unshift로 수정했었습니다! 아직 성능 최적화를 위한 코드작성 방법은 잘 모르는 상황인데 자세한 조언 감사합니다 ㅎㅎ


setLocalStorageItem("todos", todos); // update 된 객체를 localStorage 에도 update

todoInput.value = ""; // todo input 창 초기화
renderTodos(); // todo 목록 새로고침
}

addTodoBtn.addEventListener("click", handleAddTodo); // +버튼에 todo 추가하는 함수 연결

function renderTodos() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renderTodos라는 직관적인 함수명을 통해 해당 함수가 가진 역할(html 요소가 화면에 표시되는 것)을 바로 파악할 수 있어 좋습니다!🙌

const todos = getLocalStorageItem("todos"); // 저장해두었던 todo 목록 가져오기 (아직 없다면 빈 배열로 초기화)

todoList.innerHTML = ""; // 새로운 목록을 업데이트하기 위해 기존 목록 초기화
// 초기화하지 않고도 배열에 추가된 항목을 화면에 표시할 수 있지만 -> 중복 문제, 성능 문제 생길 수 있음

todos.forEach((todo, index) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forEach() 사용 좋습니다!! 👍🏻

let li = document.createElement("li"); // todo 목록에 들어있는 각각의 값 마다 li 태그 붙이기
li.innerHTML = `<span onclick="handleAddDone(${index})">${todo}</span> <i class="delete-btn" onclick="handleDeleteTodoItem(${index})"></i>`; // handleAddDone 함수와 handleDeleteTodoItem의 인자로 index를 넘겨주는 기능을 한 번에 처리하기 위함
Copy link
Member

@corinthionia corinthionia Aug 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

innerHTML은 주희 님이 사용하신 것처럼 스크립트를 문자열로 삽입할 수 있기 때문에 보안상 취약한 방법이라 여겨지고 있습니다! 따라서 createElementappend/appendChild를 사용하여 <script> 요소를 생성하고 삽입하는 방법이 더 안전한 방법이라고 합니다 👀

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드 관리 면에서 중요한 부분들을 많이 알아가는 것 같습니다! 이 부분도 참고하여 리팩토링 후 추가 커밋 하도록 하겠습니다 ㅎㅎ

todoList.appendChild(li); // 클래스인 todo-list의 자식 요소로 추가
});

todoListTitle.textContent = `📋 TO DO (${todos.length})`; // todo 목록에 들어있는 값의 개수
}

function handleDeleteTodoItem(index) {
const todos = getLocalStorageItem("todos"); // localstorage에 저장해둔 todo를 가져와서 객체로 (아직 아무것도 없다면 빈 배열로 초기화)

todos.splice(index, 1); // todos 배열 내의 index에 해당하는 값을 1개 삭제할 것

setLocalStorageItem("todos", todos); // update된 todos 배열 localStorage에 update

renderTodos(); // todo 목록에서 하나를 삭제했으므로 목록 새로고침
}

function handleAddDone(index) {
const todos = getLocalStorageItem("todos"); // localstorage에 저장해둔 todo를 가져와서 객체로 (아직 아무것도 없다면 빈 배열로 초기화)
const done = getLocalStorageItem("done"); // localstorage에 저장해둔 done를 가져와서 객체로 (아직 아무것도 없다면 빈 배열로 초기화)

const newDone = [todos[index], ...done]; // 선택된 todo를 done 배열의 맨 앞에 새로 추가
const newTodos = todos.filter((_, i) => i !== index); // Done이 된 todo의 index를 제외한 항목들만 남겨두기

setLocalStorageItem("todos", newTodos);
setLocalStorageItem("done", newDone);

renderTodos(); // update 됐으므로 새로고침
renderDone(); // update 됐으므로 새로고침
}

function renderDone() {
const done = getLocalStorageItem("done"); // localStorage에 저장해둔 done 목록을 가져옴

doneList.innerHTML = ""; // 기존 목록 초기화

done.forEach((todo, index) => {
const li = document.createElement("li"); // li 요소 생성

const span = document.createElement("span"); // span 요소 생성
span.textContent = todo; // todo 텍스트 추가

const deleteBtn = document.createElement("i"); // delete 버튼 생성
deleteBtn.classList.add("delete-btn");
deleteBtn.onclick = () => handleDeleteDoneItem(index);

// li 요소에 span과 delete 버튼 추가
li.appendChild(span);
li.appendChild(deleteBtn);

doneList.appendChild(li); // doneList에 li 요소를 추가
});

// done 목록의 제목에 완료된 항목 수를 표시
doneListTitle.textContent = `💿 DONE (${done.length})`;
}

function handleDeleteDoneItem(index) {
const done = getLocalStorageItem("done"); // localstorage에 저장해둔 done를 가져와서 객체로 (아직 아무것도 없다면 빈 배열로 초기화)
const newDone = done.filter((_, i) => i !== index);

setLocalStorageItem("done", newDone); // update된 done 목록 localStorage에도 update

renderDone(); // update 하였으므로 새로고침
}

document.addEventListener("DOMContentLoaded", () => {
// 초기 렌더링 설정을 위한 코드
// DOM이 완전히 로드된 후에 초기화되도록 설정!
// 로컬 저장소에서 가져온 데이터로 초기 상태 보여질 수 있도록 함
// 이 코드가 없다면 페이지 로드 시 초기 목록을 로드하는 데 실패할 수 있음
renderTodos();
renderDone();
});
Comment on lines +134 to +141
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아주 좋습니다 👍🏻🎉

119 changes: 118 additions & 1 deletion style.css
Original file line number Diff line number Diff line change
@@ -1 +1,118 @@
/* 자유롭게 디자인 해 주세요! */
@font-face {
font-family: "KyoboHand";
src: url("https://fastly.jsdelivr.net/gh/projectnoonnu/[email protected]/KyoboHand.woff") format("woff");
font-weight: normal;
font-style: normal;
}
* {
font-family: "KyoboHand";
}
Comment on lines +1 to +9
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

웹 폰트 적용한 부분 조아용~~

html,
body {
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(circle at 50% 0, rgba(255, 38, 38, 0.5), rgba(255, 0, 0, 0) 70.71%),
radial-gradient(circle at 6.7% 75%, rgba(30, 30, 255, 0.5), rgba(0, 0, 255, 0) 70.71%),
radial-gradient(circle at 93.3% 75%, rgba(63, 255, 63, 0.287), rgba(6, 26, 6, 0) 70.71%) beige;
}
Comment on lines +10 to +20
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

배경색 오색찬란해서 마음에 듭니다🌈


header {
margin: 18px 18px 0;
display: flex;
justify-content: center;
}

.container {
width: 360px;
height: 600px;
border-radius: 20px;
background: white;
box-shadow: 0 0 25px rgba(0, 0, 0, 0.25);
}

.input-box {
padding: 18px;
display: flex;
align-items: center;
justify-content: center;
}

#todo-list-title,
#done-list-title {
margin: 18px;
}

.todo-list,
.done-list {
width: 310px;
height: 156px;
overflow-y: scroll;
}

.delete-btn {
width: 13px;
height: 13px;
background-image: url(images/delete.png);
background-size: cover;
margin-left: 15px;
display: inline-flex;
border: none;
cursor: pointer;
}

hr {
border: 0;
height: 1px;
background: rgb(236, 234, 234);
}

#todo-input,
#todo-input:focus {
width: 80%;
height: 60%;
margin: 1%;
padding: 5%;
outline: none;
/* border-radius: 15px; */
border: 1px solid white;
border-bottom: 1px solid gray;
}

#add-todo-btn,
#add-todo-btn:hover {
height: 60%;
margin: 1%;
border: none;
border-radius: 10px;
background: none;
cursor: pointer;
}

li,
li:hover {
margin: 0 16px 16px 16px;
font-size: 16px;
cursor: pointer;
flex-direction: row;
}

/* 스크롤바의 폭 너비 */
.scrollbar::-webkit-scrollbar {
width: 10px;
}

.scrollbar::-webkit-scrollbar-thumb {
background: rgba(220, 20, 60); /* 스크롤바 색상 */
border-radius: 10px; /* 스크롤바 둥근 테두리 */
}

.scrollbar::-webkit-scrollbar-track {
background: rgba(220, 20, 60, 0.1); /*스크롤바 뒷 배경 색상*/
}
Comment on lines +107 to +119
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스크롤바 디자인까지 신경쓰시다니 멋져요 👏🏻

Comment on lines +107 to +119
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 스크롤바 디자인 방법 저도 알려주세요!

Copy link
Author

@ZUITOPIA ZUITOPIA Aug 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

링크

스크롤바 구현 자체는 여러번 해보았지만 커스텀 디자인을 해본 경험은 적어서 이번 기회를 통해 많은 글들을 찾아보았는데 그 중 가장 상세한 설명이 있는 글인 것 같아 공유합니다! 같이 공부해요 🙌


#emoji {
width: 200px;
}