-
Notifications
You must be signed in to change notification settings - Fork 11
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주차] 권혜인 미션 제출합니다 #6
base: master
Are you sure you want to change the base?
Changes from all commits
9438c31
8c8b28a
98cfce3
5a25e9c
d2c887c
da3d2c5
e24af32
e04b568
a06a97f
6330567
629db7a
17220a4
afabf04
6ce1fdc
55c60b6
1882cd9
b0b05b7
59a2ef9
24cd2dd
b44d056
1a41831
e6742d2
04f1721
07eadba
3783da8
0b089f5
9998c26
eb80030
611b55c
921ce4c
9a979a0
6ed61a4
90c5eeb
36d9c41
467cbbe
f1265ad
3bd3c64
396380d
b757d85
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,24 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<html lang="ko"> | ||
<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" /> | ||
<link rel="stylesheet" href="./style/todo.css" /> | ||
<title>혜인이의 TODO LIST</title> | ||
</head> | ||
|
||
<body> | ||
<div class="container"></div> | ||
<header> | ||
<h1 id="todayDateId"></h1> | ||
</header> | ||
<main> | ||
<div class="mainWrapper"> | ||
<section class="todoListWrapper"> | ||
<ul class="todosWrapper"></ul> | ||
</section> | ||
<button class="newTaskBtn">New Task</button> | ||
</div> | ||
</main> | ||
</body> | ||
<script src="script.js"></script> | ||
<script type="module" src="./js/main.js"></script> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
//헤더에 해당되는 날짜 상수화 파일입니다. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이렇게 상수를 따로 모듈화 해서 여러 곳에서의 재사용 성을 높이고 유지 보수를 쉽게 한 것은 매우 좋은 것 같습니다!!! 👍🏻👍🏻 |
||
|
||
// 오늘 날짜 가져오기 | ||
const currentDate = new Date(); | ||
|
||
//각각의 달, 일 가져오기 | ||
export const todayMonth = currentDate.getMonth() + 1; | ||
export const todayDAY = currentDate.getDate(); | ||
Comment on lines
+7
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
//html에 있는 dom 요소들 상수화 | ||
|
||
//header dom | ||
export const headerToday = document.getElementById("todayDateId"); | ||
|
||
//할일 dom | ||
export const todolistWrapper = document.querySelector(".todoListWrapper"); | ||
export const todosWrapper = document.querySelector(".todosWrapper"); | ||
|
||
//할일 추가 button | ||
export const newTaskBtn = document.querySelector(".newTaskBtn"); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
//new Task를 눌렀을 때 나타나는 task input | ||
const newTodoInputEmoji = document.createElement("p"); | ||
newTodoInputEmoji.textContent = "✨"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기에서 |
||
|
||
let todoInput = document.createElement("input"); | ||
todoInput.className = "todoInput"; | ||
todoInput.type = "text"; | ||
|
||
const confirmBtn = document.createElement("button"); | ||
confirmBtn.className = "confirmBtn"; | ||
confirmBtn.textContent = "확인"; | ||
|
||
let newTodoInputContainer = document.createElement("div"); | ||
newTodoInputContainer.className = "newTodoInputContainer"; | ||
newTodoInputContainer.append(newTodoInputEmoji, todoInput, confirmBtn); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아래에는 appendChild를 쓰신 부분이 있던데 append와 appendChild의 차이점 (append는 노드 객체와 문자열 모두 추가할 수 있는 반면, appendChild는 노드 객체만 추가 가능하다 & append는 한번에 여러개의 자식 노드를 추가할 수 있지만, appendChild는 한번에 한 개만 가능하다.) 을 잘 이해하고 계신 듯 합니다! 👍🏻 저는 기존에 appendChild만 알고 있었고 그것만 사용했는데... 제가 수정이 필요할 것 같네용 😂 |
||
|
||
export { todoInput, confirmBtn, newTodoInputContainer }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { headerInsertDate } from "./utils/header.js"; | ||
import { newTaskFn } from "./utils/makeNewTask.js"; | ||
import { showTask } from "./utils/showTask.js"; | ||
|
||
newTaskFn(); | ||
headerInsertDate(); | ||
showTask(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { todosWrapper } from "../constants/document.js"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이렇게 기능별 함수를 모듈화 해서 사용하신 것도 위의 상수 모듈화와 동일하게 유지보수에 강점을 가지고 있는 것 같습니다 👍🏻 |
||
import { handleDeleteStoreTask } from "./storeTask.js"; | ||
|
||
//element 추가 함수 | ||
export function handleNewTask(todoId, todoValue) { | ||
let doneBtnContainer = document.createElement("div"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 제가 보기에는 |
||
doneBtnContainer.className = "doneBtnContainer"; | ||
|
||
let doneBtn = document.createElement("input"); | ||
doneBtn.id = todoId; | ||
doneBtn.type = "checkbox"; | ||
|
||
let doneBtnLabel = document.createElement("label"); | ||
doneBtnLabel.className = "doneBtnLabel"; | ||
doneBtnLabel.htmlFor = doneBtn.id; | ||
|
||
doneBtnContainer.append(doneBtn, doneBtnLabel); | ||
|
||
let deleteBtn = document.createElement("button"); | ||
deleteBtn.className = "deleteBtn"; | ||
deleteBtn.type = "button"; | ||
deleteBtn.textContent = "🗑️"; | ||
//삭제 | ||
deleteBtn.addEventListener("click", () => { | ||
onetodoWrapper.remove(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 삭제 버튼 클릭 시 onetodoWrapper.remove()를 호출하셨는데, onetodoWrapper 변수가 선언되기 전에 사용하신 것을 보아 JS의 호이스팅을 이용하신 것 같습니당 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +) 추가로, todo 삭제 시 alert나 'confirm('이 할 일을 삭제하시겠습니까?')' 등을 사용해 사용자에게 알리는 것은 어떨까용?! 이 점은 저도 수정하려고 합니다! 저는 단순히 alert만으로 삭제한다는 것을 알려주기만 하고 '취소' 선택지를 두지 않았었는데 confirm을 사용해 사용자가 선택할 수 있게 하면 더 좋을 것 같습니다!! 👍🏻👍🏻 |
||
handleDeleteStoreTask(todoId); | ||
}); | ||
|
||
let onetodoWrapper = document.createElement("li"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 |
||
onetodoWrapper.className = "onetodoWrapper"; | ||
|
||
let todoContent = document.createElement("p"); | ||
todoContent.className = "todoContent"; | ||
|
||
onetodoWrapper.append(doneBtnContainer, todoContent, deleteBtn); | ||
|
||
//할일 입력됨 | ||
todoContent.textContent = todoValue; | ||
|
||
doneBtn.addEventListener("change", () => { | ||
if (doneBtn.checked) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 혜인 님의 todolist를 사용해 보았는데 todo를 하나 생성하고 완료 표시를 누른 뒤 새로 고침을 하면 체크 표시가 초기화 되더라고요 😂😂 완료 상태 또한 localStorage에 저장해서 새로 고침되어도 상태가 유지될 수 있게 하는 방식은 어떨까요?! 그러기 위해서는 아이디와 값만을 저장하는 것이 아니라 배열 형태로 list를 저장하는 것을 추천 드립니다...!! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 헐 |
||
todoContent.style.textDecoration = "line-through"; | ||
} else { | ||
todoContent.style.textDecoration = "none"; | ||
} | ||
}); | ||
todosWrapper.append(onetodoWrapper); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { todayDAY, todayMonth } from "../constants/date.js"; | ||
import { headerToday } from "../constants/document.js"; | ||
|
||
//각 날짜 불러오기 | ||
export function headerInsertDate() { | ||
headerToday.append(todayMonth, "월", " ", todayDAY, "일"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기에서는 텍스트를 하나하나 headerToday.textContent = `${todayMonth}월 ${todayDAY}일` 템플릿 리터럴을 이용하면 어디에 변수가 들어갈 지 알기 쉬워서 좋은 것 같아요😘 |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { newTaskBtn, todolistWrapper } from "../constants/document.js"; | ||
import { confirmBtn, newTodoInputContainer } from "../constants/newTask.js"; | ||
import { handleNewTask } from "./handleNewTasks.js"; | ||
import { todoInput } from "../constants/newTask.js"; | ||
import { handleStoreTask } from "./storeTask.js"; | ||
|
||
//NewTask 버튼 눌렀을 때 | ||
function onClickNewTaskBtn() { | ||
newTaskBtn.addEventListener("click", () => { | ||
todolistWrapper.appendChild(newTodoInputContainer); | ||
}); | ||
} | ||
|
||
//확인 눌렀을 때 | ||
function onClickConfirmBtn() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 todoInput.value에 대한 유효성 검사가 없어서 빈 입력값도 저장되고 있습니다 😂 입력 값이 비어있는 경우 할 일이 추가되지 않도록
이렇게 입력 값이 비었으면 함수가 종료되도록 수정하는 것은 어떨까요?! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +) 확인 버튼을 클릭해야만 새로운 todo가 생기는 것이 아니라 enter 키를 눌렀을 때도 새로운 todo가 생기도록 추가하면 어떨까요!? 제가 혜인 님의 todolist를 테스트하면서 습관적으로 enter을 치고 있더라고요 😂😂 저와 같은 사용자를 위해 enter로도 todo가 추가되도록 todoInput 요소에 keydown 이벤트 리스너를 추가하여 엔터 키를 감지하고, 엔터 키가 눌렸을 때 새로운 todo를 추가하는 함수를 호출하는 방식을 추천 드립니다...!!! (더 나은 방식이 있다면 채택 후 공유 부탁드립니다 👍🏻👍🏻👍🏻) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그러네요! 제가 사용자 입장에서 생각할 시간이 많이 부족했던 것 같습니다ㅠㅠ! 생각도 못한 부분들 짚어주셔서 넘넘 감사합니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 input, button 태그를 form으로 감싼 다음 |
||
confirmBtn.addEventListener("click", (e) => { | ||
let todoId = Math.random(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 고유 id 생성을 위해 Math.random()을 사용하셨는데, 이 방식은 완전히 고유성을 보장하지 않기 때문에 같은 ID가 생성될 가능성이 (아주 낮지만) 존재하며, 이런 경우 기능이 의도한 대로 작동하지 않을 수 있다고 알고 있습니다! UUID나 Date.now()를 활용하는 방식 등 더 고유성을 보장하는 방법을 사용하는 것으로 개선하는 것은 어떨까요?! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
의견감사합니당 |
||
handleNewTask(todoId, todoInput.value); | ||
handleStoreTask(todoId, todoInput.value); | ||
todoInput.value = ""; | ||
}); | ||
} | ||
|
||
function newTaskFn() { | ||
onClickNewTaskBtn(); | ||
onClickConfirmBtn(); | ||
} | ||
|
||
export { newTaskFn }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { handleNewTask } from "./handleNewTasks.js"; | ||
|
||
export function showTask() { | ||
if (localStorage.length > 0) { | ||
for (let i = 0; i < localStorage.length; i++) { | ||
const key = localStorage.key(i); | ||
const value = localStorage.getItem(key); | ||
if (value) { | ||
handleNewTask(key, value); | ||
} | ||
} | ||
Comment on lines
+3
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
if (localStorage.length === 0) {
return;
}
for (let i = 0; i < localStorage.length; i++) {
...
} 처럼 early return을 하려고 해요! 코드의 depth를 줄일 수 있어서 과도한 들여쓰기를 피할 수 있거든요!! 물론 상황에 따라서 장단점이 있으니 한번 알아보시면 좋을 것 같아요!!
Object.keys(localStorage).forEach(key => {
const value = localStorage.getItem(key);
if (value) {
handleNewTask(key, value);
}
}); |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export function handleStoreTask(id, value) { | ||
window.localStorage.setItem(id, value); | ||
} | ||
|
||
export function handleDeleteStoreTask(id) { | ||
window.localStorage.removeItem(id); | ||
} |
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
.doneBtnContainer { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. css 파일도 역할별로 모듈화 하니 유지보수에 정말 좋을 것 같습니다!! 👍🏻 저는 하나의 css 파일에 모든 디자인 요소를 모아두는 바람에 항상 ctrl F를 사용했었는데 ㅠㅠ 다음에는 저도 모듈화에 좀 더 힘을 써야겠다고 느껴지네용 👍🏻👍🏻 |
||
box-sizing: border-box; | ||
--background-color: #fff; | ||
--checkbox-height: 10px; | ||
} | ||
|
||
@-moz-keyframes dothabottomcheck-19 { | ||
0% { | ||
height: 0; | ||
} | ||
100% { | ||
height: calc(var(--checkbox-height) / 2); | ||
} | ||
} | ||
|
||
@-webkit-keyframes dothabottomcheck-19 { | ||
0% { | ||
height: 0; | ||
} | ||
100% { | ||
height: calc(var(--checkbox-height) / 2); | ||
} | ||
} | ||
|
||
@keyframes dothabottomcheck-19 { | ||
0% { | ||
height: 0; | ||
} | ||
100% { | ||
height: calc(var(--checkbox-height) / 2); | ||
} | ||
} | ||
|
||
@keyframes dothatopcheck-19 { | ||
0% { | ||
height: 0; | ||
} | ||
50% { | ||
height: 0; | ||
} | ||
100% { | ||
height: calc(var(--checkbox-height) * 1.2); | ||
} | ||
} | ||
|
||
@-webkit-keyframes dothatopcheck-19 { | ||
0% { | ||
height: 0; | ||
} | ||
50% { | ||
height: 0; | ||
} | ||
100% { | ||
height: calc(var(--checkbox-height) * 1.2); | ||
} | ||
} | ||
|
||
@-moz-keyframes dothatopcheck-19 { | ||
0% { | ||
height: 0; | ||
} | ||
50% { | ||
height: 0; | ||
} | ||
100% { | ||
height: calc(var(--checkbox-height) * 1.2); | ||
} | ||
} | ||
|
||
.doneBtnContainer input[type="checkbox"] { | ||
display: none; | ||
} | ||
|
||
.doneBtnContainer .doneBtnLabel { | ||
height: var(--checkbox-height); | ||
width: var(--checkbox-height); | ||
background-color: transparent; | ||
border: calc(var(--checkbox-height) * 0.1) solid #000; | ||
border-radius: 3px; | ||
position: relative; | ||
display: inline-block; | ||
-moz-box-sizing: border-box; | ||
-webkit-box-sizing: border-box; | ||
box-sizing: border-box; | ||
-moz-transition: border-color ease 0.2s; | ||
-o-transition: border-color ease 0.2s; | ||
-webkit-transition: border-color ease 0.2s; | ||
transition: border-color ease 0.2s; | ||
cursor: pointer; | ||
} | ||
.doneBtnContainer .doneBtnLabel::before, | ||
.doneBtnContainer .doneBtnLabel::after { | ||
-moz-box-sizing: border-box; | ||
-webkit-box-sizing: border-box; | ||
box-sizing: border-box; | ||
position: absolute; | ||
height: 0; | ||
width: calc(var(--checkbox-height) * 0.2); | ||
background-color: #d88fd8; | ||
display: inline-block; | ||
-moz-transform-origin: left top; | ||
-ms-transform-origin: left top; | ||
-o-transform-origin: left top; | ||
-webkit-transform-origin: left top; | ||
transform-origin: left top; | ||
border-radius: 5px; | ||
content: " "; | ||
-webkit-transition: opacity ease 0.5; | ||
-moz-transition: opacity ease 0.5; | ||
transition: opacity ease 0.5; | ||
} | ||
.doneBtnContainer .doneBtnLabel::before { | ||
top: calc(var(--checkbox-height) * 0.72); | ||
left: calc(var(--checkbox-height) * 0.41); | ||
box-shadow: 0 0 0 calc(var(--checkbox-height) * 0.05) var(--background-color); | ||
-moz-transform: rotate(-135deg); | ||
-ms-transform: rotate(-135deg); | ||
-o-transform: rotate(-135deg); | ||
-webkit-transform: rotate(-135deg); | ||
transform: rotate(-135deg); | ||
} | ||
.doneBtnContainer .doneBtnLabel::after { | ||
top: calc(var(--checkbox-height) * 0.37); | ||
left: calc(var(--checkbox-height) * 0.05); | ||
-moz-transform: rotate(-45deg); | ||
-ms-transform: rotate(-45deg); | ||
-o-transform: rotate(-45deg); | ||
-webkit-transform: rotate(-45deg); | ||
transform: rotate(-45deg); | ||
} | ||
|
||
.doneBtnContainer input[type="checkbox"]:checked + .doneBtnLabel, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 체크 박스 스타일에 많은 신경을 쓰신 것으로 보입니다!! 👏🏻👏🏻 정말 체크할 때마다 체크 표시가 느리게 나타나고 체크 박스 색이 변화하는 것은 시각적으로 정말 매력적인 것 같아요 👍🏻 |
||
.doneBtnContainer .doneBtnLabel.checked { | ||
border-color: #d88fd8; | ||
} | ||
.doneBtnContainer input[type="checkbox"]:checked + .doneBtnLabel::after, | ||
.doneBtnContainer .doneBtnLabel.checked::after { | ||
height: calc(var(--checkbox-height) / 2); | ||
-moz-animation: dothabottomcheck-19 0.2s ease 0s forwards; | ||
-o-animation: dothabottomcheck-19 0.2s ease 0s forwards; | ||
-webkit-animation: dothabottomcheck-19 0.2s ease 0s forwards; | ||
animation: dothabottomcheck-19 0.2s ease 0s forwards; | ||
} | ||
.doneBtnContainer input[type="checkbox"]:checked + .doneBtnLabel::before, | ||
.doneBtnContainer .doneBtnLabel.checked::before { | ||
height: calc(var(--checkbox-height) * 1.2); | ||
-moz-animation: dothatopcheck-19 0.4s ease 0s forwards; | ||
-o-animation: dothatopcheck-19 0.4s ease 0s forwards; | ||
-webkit-animation: dothatopcheck-19 0.4s ease 0s forwards; | ||
animation: dothatopcheck-19 0.4s ease 0s forwards; | ||
Comment on lines
+136
to
+150
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이렇게 |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
시멘틱 태그의 장점으로 코드 가독성 및 유지보수가 용이하며 검색 엔진에 최적화된다는 것이 있는데 혜인 님의 html은 장점을 잘 살려 시멘틱 태그를 활용해 주신 듯 합니다!