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주차] 심여은 미션 제출합니다. #5

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
89e77a0
✨ Feat : 기본 html 구조 작성
ongheong Aug 27, 2024
db6fbdf
💄 Feat : 웹 폰트 추가
ongheong Aug 27, 2024
70fcdc8
✨ Feat : 사용자 입력을 localStorage에 저장, 업데이트, 화면 출력 기능 추가
ongheong Aug 27, 2024
0ec49be
🐛 Fix : Enter 키 입력 시 화면이 새로고침되는 문제 수정
ongheong Aug 27, 2024
ce263a0
🔨 Refactor : html에서 onclick 이벤트 제거 -> addEventListener로 바꿈
ongheong Aug 27, 2024
8dec920
✨ Feat : todo item의 done, delete 기능 추가
ongheong Aug 27, 2024
ebf9167
🐛 Fix : localStorage의 순서와 화면 출력 순서가 다른 오류 해결
ongheong Aug 27, 2024
a1d5cdd
🏗️ Feat : 배경 이미지 업로드
ongheong Aug 27, 2024
5a3602c
💄 Refactor : 버튼의 innerText 수정
ongheong Aug 27, 2024
ca63868
💄 Refactor : label 태그 추가, h4 태그 innerText 수정
ongheong Aug 27, 2024
82d8789
💄 Feat : 각 태그들의 스타일 1차 작성
ongheong Aug 27, 2024
142f541
💄 Refactor : 스크롤바 추가
ongheong Aug 27, 2024
aab51c6
🐛 Fix : 빈 문자열 입력 -> 유효성 검사 추가
ongheong Aug 27, 2024
32c826e
✨ Feat : 하위 li 태그의 개수를 출력하는 기능 추가
ongheong Aug 27, 2024
f43362a
🐛 Fix : 할 일 완료 시 Done의 개수가 업데이트되지 않는 오류 수정
ongheong Aug 27, 2024
6379d37
💬 Style : 불필요한 주석 삭제 및 수정
ongheong Aug 27, 2024
0d17840
🐛 Fix : li가 없을 경우 section title이 없는 오류 해결
ongheong Aug 28, 2024
0546dc2
💄 Refactor : body, container, ul의 높이 수정
ongheong Aug 28, 2024
c46b278
💄 Refactor : + 버튼 클릭 시 hover 효과 추가
ongheong Aug 28, 2024
1f3acc7
🔧 Style : .prettier 설정 추가
ongheong Aug 29, 2024
61c6b32
Update script.js
ongheong Aug 30, 2024
956b42c
changes committed
ongheong Aug 30, 2024
0bd49ca
♻️ Refactor: 배열내장함수(filter, foreach 등)와 spread 문법 사용, newTodo 객체에 cre…
ongheong Sep 3, 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 grid.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 18 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,25 @@
<title>Vanilla Todo</title>
<link rel="stylesheet" href="style.css" />
</head>

<body>
<div class="container"></div>
<div class="container">
<header>Todo List</header>
<form id="input-form" onsubmit="return false;">
<label for="todo-input">할 일 : </label>
<input id="todo-input" placeholder="할 일을 입력하세요" />
<button id="input-btn">+</button>
</form>
<section id="todo-list-section">
<h4 class="section-title" id="todo-num">📋 Todo (0)</h4>
<ul id="todo-list">
</ul>
</section>
<section id="done-list-section">
<h4 class="section-title done" id="done-num">👍🏻 Done (0)</h4>
<ul id="done-list">
</ul>
</section>
</div>
</body>
<script src="script.js"></script>
</html>
113 changes: 112 additions & 1 deletion script.js
Original file line number Diff line number Diff line change
@@ -1 +1,112 @@
// 다들 화이팅!! ٩( *˙0˙*)۶
// 다들 화이팅!! ٩( *˙0˙*)۶

window.onload = function () {
// 창을 새로고침해도 todo list가 유지되도록 하기
todoListElement.innerHTML = "";
getTodoList().forEach((todo) => {
createTodoElement(todo);
});
};

const todoInputElement = document.getElementById("todo-input");
const todoInputButton = document.getElementById("input-btn");
const todoListElement = document.getElementById("todo-list");
const doneListElement = document.getElementById("done-list");

// input 버튼 클릭 시
todoInputButton.addEventListener("click", () => {
if (todoInputElement.value === "") {
//입력값이 없을 때
alert("한 글자 이상 입력해주세요!");
return;
}

const newTodo = {
content: todoInputElement.value,
complete: false,
};

setTodoList(newTodo); //local storage에 저장
createTodoElement(newTodo); //새로운 todo element를 화면에 출력
todoInputElement.value = ""; // input value 초기화
});

// local storage에 저장된 값을 배열로 불러오기
function getTodoList() {
const todoList = localStorage.getItem("todoList");
return todoList ? JSON.parse(todoList) : [];
}

// local storage에 todo list 저장하기
function setTodoList(todo) {
let newTodoList = getTodoList();
//todo done 여부 확인
for (let i = 0; i < newTodoList.length; i++) {
if (
newTodoList[i].content === todo.content &&
newTodoList[i].complete !== todo.complete
) {
newTodoList.splice(i, 1);
break;
}
}
newTodoList.push(todo);
Copy link
Member

Choose a reason for hiding this comment

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

이 부분을 JavaScript의 배열 내장 함수를 사용하여 자바스크립트 답게 작성해 보면 좋을 것 같아요! 또한 JavaScript에서 배열을 사용할 때 splice()push()를 사용하는 것보다는 새로운 배열을 만들어 할당하는 방식을 자주 사용합니다 🙂


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

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

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

Choose a reason for hiding this comment

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

저도 주현님께서 말씀해주신 것과 같이 배열 내장 함수를 사용하는 것이 좋을 것 같습니다! 훨씬 간결해질 것 같습니다!

Copy link
Member Author

@ongheong ongheong Aug 29, 2024

Choose a reason for hiding this comment

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

배열 내장 함수 바로 적용해서 자바에서 자바스크립트로 업그레이드 해보겠습니다!
JS의 불변성에 대해서는 잘 몰랐는데, react에서 state 객체에 불변성을 적용하는 것도 같은 맥락이네요! 또 새롭게 알아갑니다😀
splice()를 사용한 코드는 map()을 사용해서 리팩토링해보겠습니다!

localStorage.setItem("todoList", JSON.stringify(newTodoList));
}

// todo element 생성
function createTodoElement(todo) {
const newLi = document.createElement("li");
const newDoneBtn = document.createElement("button");
const newSpan = document.createElement("span");
const newDeleteBtn = document.createElement("button");

newLi.appendChild(newDoneBtn);
newLi.appendChild(newSpan);
newLi.appendChild(newDeleteBtn);

newSpan.textContent = todo.content;

//완료 버튼 클릭 시
newDoneBtn.addEventListener("click", () => {
todo.complete = !todo.complete;
setTodoList(todo);
createTodoElement(todo);
newLi.remove();
updateItemCount();
});

//삭제 버튼 클릭 시
newDeleteBtn.addEventListener("click", () => {
const newTodoList = getTodoList();
const index = newTodoList.indexOf(todo);
newTodoList.splice(index, 1);
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.

여기도 추후에 filter() 메서드를 사용해 보면 좋을 것 같아요!

localStorage.setItem("todoList", JSON.stringify(newTodoList));
newLi.remove();
updateItemCount();
});

//완료 여부에 따라 todoList / doneList에 추가
if (todo.complete) {
newDoneBtn.innerText = "v";
doneListElement.appendChild(newLi);
} else {
newDoneBtn.innerHTML = "&nbsp;";
todoListElement.appendChild(newLi);
}

newDeleteBtn.textContent = "x";
Copy link
Member

Choose a reason for hiding this comment

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

innerText와 textContent를 구분해서 사용하신 이유가 있으신가요 ?

Copy link
Member Author

Choose a reason for hiding this comment

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

앗 따로 이유는 없습니다! 코드를 꼼꼼하게 확인하지 못해서 같은 기능에 다른 속성을 사용하고 있었네요ㅠㅠ😮‍💨 확인 감사합니다!🙇🏻‍♀️
button 엘리먼트 하나의 텍스트값만 읽어오니까 innerText로 통일해서 사용하는게 맞겠네요!

updateItemCount();
}

// todo, done 개수 업데이트
function updateItemCount() {
const todoNum = document.getElementById("todo-num");
const doneNum = document.getElementById("done-num");
todoNum.textContent = `📋 Todo (${
todoListElement.getElementsByTagName("li").length
})`;
doneNum.textContent = `👍🏻 Done (${
doneListElement.getElementsByTagName("li").length
})`;
}
171 changes: 170 additions & 1 deletion style.css
Original file line number Diff line number Diff line change
@@ -1 +1,170 @@
/* 자유롭게 디자인 해 주세요! */
/* 자유롭게 디자인 해 주세요! */
@font-face {
font-family: 'RixXladywatermelonR';
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/[email protected]/RixXladywatermelonR.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}

html {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}

body {
font-family: RixXladywatermelonR;
max-width: 500px;
padding: 10px;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
scrollbar-width: thin;
}

.container {
position: relative;
display: flex;
width: 450px;
height: 680px;
flex-direction: column;
background-color: #fef79fbd;
overflow: hidden;
}

.container::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url("./grid.jpg");
background-size: cover;
background-blend-mode: darken;
opacity: 0.5;
z-index: -1;
}

header {
font-size: 40px;
font-weight: bold;
padding: 20px;
background-color: #2b3681;
color: white;
box-shadow: 0 8px 10px -5px rgba(0, 0, 0, 0.5);
}

form {
padding: 10px;
display: flex;
justify-content: center;
align-items: baseline;
gap: 10px;
margin-top: 20px;
}

label {
align-items: center;
padding: 2px 7px;
font-size: 20px;
}

input {
border-color: black;
border-width: 0 0 2px;
background-color: transparent;
width: 180px;
margin: 0;
font-family: RixXladywatermelonR;
padding-bottom: 5px;
color:rgb(50, 50, 50);
}

input:focus {
outline: none;
box-shadow: none;
}

button {
font-family: RixXladywatermelonR;
display: flex;
justify-content: center;
align-content: center;
flex-wrap: wrap-reverse;
}
Comment on lines +92 to +98
Copy link
Member

Choose a reason for hiding this comment

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

cursor: pointer 를 추가하면 더 좋을 것 같습니다 😉

Copy link
Member Author

@ongheong ongheong Aug 30, 2024

Choose a reason for hiding this comment

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

오 버튼에 추가해보겠습니다!


#input-btn {
border: transparent;
border-radius: 4px;
background-color: transparent;
padding: 2px 7px;
font-size: 25px;
font-weight: 500;
}

#input-btn:hover {
color: green;
background-color: transparent;
}

section {
margin-top: 20px;
}

h4 {
font-size: 20px;
text-align: left;
padding: 10px 20px;
margin: 0;
}

ul {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
align-items: flex-start;
height: 168px;
overflow-y: scroll;
}

li {
display: flex;
align-items: center;
padding: 8px 30px;
gap: 8px;
}

li > :nth-child(1) {
width: 25px;
height: 25px;
border-radius: 8px;
background-color: transparent;
font-size: 25px;
border-color: #393939;
}

li > :nth-child(3) {

Choose a reason for hiding this comment

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

css 정석대로 야무지게 잘 사용하시네요 멋집니다!

border: transparent;
background-color: transparent;
font-size: 20px;
font-weight: 500;
}

#done-list span {
text-decoration: line-through;
color: #b3b3b3;
}

#done-list li > :nth-child(1) {
border: transparent;
background-color: green;
font-size: 20px;
font-weight: 500;
color: white;
}