-
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주차] 최지원 미션 제출합니다. #3
base: master
Are you sure you want to change the base?
Changes from 13 commits
2174809
3556734
1434121
8ec025d
e3313a3
a254567
3152ab0
4de67aa
8f7e29b
c08b6fa
86260ea
3d0989b
ca4e76e
3d044fe
28d46e6
970222b
cd12e36
780ce6d
a8f23ca
eb9f93e
9ef7e0e
f6d8336
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 |
---|---|---|
|
@@ -3,12 +3,52 @@ | |
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Vanilla Todo</title> | ||
<title>TodoList</title> | ||
<link | ||
rel="stylesheet" | ||
href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css" | ||
/> | ||
<link rel="stylesheet" href="style.css" /> | ||
<link rel="icon" href="images/full_checkbox.png" /> | ||
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. 소소한 포인트였는데 발견해주시다니 기쁩니다..😄 사실 파비콘으로는 ico 파일이 많이 쓰인다고 알고 있는데, 제가 가진 png는 ico 변환기를 통해 사용해보니 깨지는 것 같더라구요. 그래도 ico 파일이 여러 사이즈를 저장하고 있어서 더 적절하다고 알고 있습니다! |
||
</head> | ||
|
||
<body> | ||
<div class="container"></div> | ||
<div class="container"> | ||
<header> | ||
<h1 class="today"></h1> | ||
</header> | ||
<nav class="box-style"> | ||
<span class="ment-style">당신의 할 일을 작성해보세요 ✏️</span> | ||
<hr /> | ||
<button class="show-input ment-style">입력창 불러오기</button> | ||
</nav> | ||
<main class="box-style"> | ||
<section class="todo"> | ||
<div class="todo-top"> | ||
<h2 class="ment-style">What I have to do</h2> | ||
<form class="input-box box-style" style="display: none"> | ||
<input class="input" /> | ||
<button class="bold-style">작성하기</button> | ||
</form> | ||
</div> | ||
<ul class="todoList"></ul> | ||
</section> | ||
<section class="did"> | ||
<h2 class="ment-style">What I did</h2> | ||
<ul class="didList"></ul> | ||
</section> | ||
</main> | ||
<footer> | ||
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. footer main 등등 시맨틱 태그들을 적절히 잘 활용해주셨네요!! 문서의 구조를 보다 더 명확하게 파악할 수 있는 것 같습니다 ㅎㅎ |
||
<span> | ||
<span class="today">Total: </span> | ||
<span class="count bold-style"></span> | ||
</span> | ||
<span> | ||
<span class="today">Accomplishment: </span> | ||
<span class="accomplishment bold-style"></span> | ||
</span> | ||
</footer> | ||
</div> | ||
</body> | ||
<script src="script.js"></script> | ||
</html> |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1 +1,194 @@ | ||||||||||||||||||||||||||||||
//😍CEOS 20기 프론트엔드 파이팅😍 | ||||||||||||||||||||||||||||||
/* 날짜 및 현재 시각 */ | ||||||||||||||||||||||||||||||
const updateTime = () => { | ||||||||||||||||||||||||||||||
const today = document.querySelector(".today"); | ||||||||||||||||||||||||||||||
const now = new Date(); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const options = { | ||||||||||||||||||||||||||||||
weekday: "long", | ||||||||||||||||||||||||||||||
year: "numeric", | ||||||||||||||||||||||||||||||
month: "long", | ||||||||||||||||||||||||||||||
day: "numeric", | ||||||||||||||||||||||||||||||
hour: "2-digit", | ||||||||||||||||||||||||||||||
minute: "2-digit", | ||||||||||||||||||||||||||||||
second: "2-digit", | ||||||||||||||||||||||||||||||
hour12: false, | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// 요일, 날짜 및 시각 포맷 적용 | ||||||||||||||||||||||||||||||
today.innerHTML = now.toLocaleString("ko-KR", options); | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
setInterval(updateTime, 1000); // 1초마다 호출 | ||||||||||||||||||||||||||||||
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. 호출 시 즉시 실행시키면 첫 1초를 기다리지 않아서 이런식으로 구성해도 좋을 것 같습니다.
Suggested change
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. 단순 로딩 문제라 생각했는데 앞으로는 쓰려는 함수의 작동을 정확히 이해하고 써야할 것 같습니다😂 감사합니다! |
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
/* localStorage 데이터 처리 */ | ||||||||||||||||||||||||||||||
// localStorage에 항목 저장하기 함수 | ||||||||||||||||||||||||||||||
const saveToLocalStorage = (key, data) => { | ||||||||||||||||||||||||||||||
localStorage.setItem(key, JSON.stringify(data)); | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// localStorage의 항목 불러오기 함수 | ||||||||||||||||||||||||||||||
const loadFromLocalStorage = (key) => { | ||||||||||||||||||||||||||||||
const data = localStorage.getItem(key); | ||||||||||||||||||||||||||||||
return data ? JSON.parse(data) : []; | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
let todos = loadFromLocalStorage("todos"); // 할 일 배열 | ||||||||||||||||||||||||||||||
let dones = loadFromLocalStorage("dones"); // 한 일 배열 | ||||||||||||||||||||||||||||||
Comment on lines
+36
to
+37
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. todos와 dones는 배열을 변경할 필요가 없으므로, const를 사용해도 좋을 것같습니다! let은 값이 재할당되거나 변동될 가능성이 있는 경우에만 사용하는 것이 좋다고 합니다. :) 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을 사용하고선 넘어갔는데, 주신 리뷰보고서 한 번 더 디버깅 해보면서 정확히 어디까진 동작하고 어느 지점부터 재할당으로 인해 반영이 안되는지 파악해볼 수 있었습니다.
Comment on lines
+36
to
+37
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. 로컬스토리지에 key값을 두 개로 분리하여 저장하는 방법을 배워갑니다! 호기심에 다른 방법은 없을까 찾아보다 |
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// localStorage의 항목으로 초기화하기 함수 | ||||||||||||||||||||||||||||||
const initTodoList = () => { | ||||||||||||||||||||||||||||||
todos.forEach((todo) => printItem(todo, "todo")); | ||||||||||||||||||||||||||||||
dones.forEach((done) => printItem(done, "did")); | ||||||||||||||||||||||||||||||
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. 완료된 항목을 나타내기 위한 표현으로 did와 done이 혼용되어있어서 하나로 통일하는 것이 좋지 않을까 해요!! |
||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// 할 일과 한 일 개수 및 성취도 업데이트 함수 | ||||||||||||||||||||||||||||||
const updateCounts = () => { | ||||||||||||||||||||||||||||||
const totalCount = todos.length + dones.length; | ||||||||||||||||||||||||||||||
const doneCount = dones.length; | ||||||||||||||||||||||||||||||
const countElement = document.querySelector(".count"); | ||||||||||||||||||||||||||||||
const accomplishmentElement = document.querySelector(".accomplishment"); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
countElement.innerText = totalCount; | ||||||||||||||||||||||||||||||
accomplishmentElement.innerText = | ||||||||||||||||||||||||||||||
totalCount > 0 ? `${doneCount}/${totalCount}` : "0/0"; | ||||||||||||||||||||||||||||||
Comment on lines
+51
to
+54
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. 삼항조건문으로 count 처리를 하는 게 인상깊습니다! 저도 최근에 알게 된 사실인데 innerText와 textContent로 text를 넣는 차이가 있다는 것을 알았습니다. 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. innerHTML과 innerText만 알고 있었는데 말씀해주신 각 개념과 차이점까지 분명하게 정리하고 넘어갑니다👍 감사합니다! |
||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
/* 공용 함수 */ | ||||||||||||||||||||||||||||||
// 버튼 생성하기 함수 | ||||||||||||||||||||||||||||||
const createBtn = (src, className, clickHandler) => { | ||||||||||||||||||||||||||||||
const btn = document.createElement("button"); | ||||||||||||||||||||||||||||||
const img = document.createElement("img"); | ||||||||||||||||||||||||||||||
img.setAttribute("src", src); | ||||||||||||||||||||||||||||||
btn.appendChild(img); | ||||||||||||||||||||||||||||||
btn.setAttribute("class", className); | ||||||||||||||||||||||||||||||
btn.addEventListener("click", clickHandler); | ||||||||||||||||||||||||||||||
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. 각 버튼에 대해 개별적으로 이벤트 리스너를 추가하는 방식으로 작성해주셨는데, 때문에, 상위 요소에 이벤트 리스너를 등록하고, 실제 클릭된 하위 요소(버튼)는 event.target을 통해 받아와서 적절한 로직을 취하는 것을 제안드려요! 이 방식을 취하면, 버튼 클릭 처리 로직과 / 버튼 생성 createBtn , 항목 출력하기 printItem 로직을 분리할 수 있어서, 나중에 유지보수할때도 좋고, 코드의 가독성도 좋아질 것 같아요. 먼저 .todoList, .didList에 한 번만 이벤트 리스너를 등록한 후에, `수정 코드 첨부합니다!! document.querySelector('.didList').addEventListener('click', (e) => { // 버튼 생성하기 함수 (이벤트 리스너 없이 생성) // 항목 출력하기 함수 (버튼에 이벤트 리스너 추가x) // 체크 버튼 생성 // 삭제 버튼 생성 itemContent.className = "todo-item"; item.appendChild(itemContent); 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. 감사합니다. 수정코드 방향으로 고쳐보며 다시 한번 이벤트 위임의 개념을 다져봐야 할 것 같습니다🥹 디테일하게 리뷰해주셔서 더 많이 고민해보는 계기도 되었고 정말 많이 배워갑니다🙇♀️!! |
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
return btn; | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// 항목 출력하기 함수 | ||||||||||||||||||||||||||||||
const printItem = (text, type) => { | ||||||||||||||||||||||||||||||
const list = document.querySelector(`.${type}List`); | ||||||||||||||||||||||||||||||
const item = document.createElement("li"); | ||||||||||||||||||||||||||||||
const itemContent = document.createElement("div"); | ||||||||||||||||||||||||||||||
const itemText = document.createElement("span"); | ||||||||||||||||||||||||||||||
itemText.innerText = text; | ||||||||||||||||||||||||||||||
itemText.className = `${type}-text`; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// 체크 버튼 생성하기 | ||||||||||||||||||||||||||||||
const checkBtn = createBtn( | ||||||||||||||||||||||||||||||
type === "todo" ? "images/empty_checkbox.png" : "images/full_checkbox.png", | ||||||||||||||||||||||||||||||
"todo-check", | ||||||||||||||||||||||||||||||
type === "todo" ? todoToDid : didToTodo | ||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||
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. 조건문으로 printItem에서 type에 따라 다른 이미지를 사용하는 구성이 정말 깔끔한 로직인 것 같습니다! 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. check box 디자인을 위해 사진을 활용하는 방법은 생각해보지 못 했던 것 같습니다. 새로운 배움주셔 감사합니다! |
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// 삭제 버튼 생성하기 | ||||||||||||||||||||||||||||||
const deleteBtn = createBtn( | ||||||||||||||||||||||||||||||
"images/delete_btn.png", | ||||||||||||||||||||||||||||||
"todo-del", | ||||||||||||||||||||||||||||||
type === "todo" ? deleteTodoItem : deleteDidItem | ||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// 항목 구성하기 | ||||||||||||||||||||||||||||||
itemContent.className = "todo-item"; | ||||||||||||||||||||||||||||||
itemContent.appendChild(checkBtn); | ||||||||||||||||||||||||||||||
itemContent.appendChild(itemText); | ||||||||||||||||||||||||||||||
itemContent.appendChild(deleteBtn); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
item.appendChild(itemContent); | ||||||||||||||||||||||||||||||
list.appendChild(item); | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// 할 일 추가하기 함수 | ||||||||||||||||||||||||||||||
const addTodoItem = (event) => { | ||||||||||||||||||||||||||||||
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 또는 timestamp)를 추가하여 각 항목을 고유하게 식별하는 것을 추천드려요!
Suggested change
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로 배열의 요소를 관리하는 것은 어떻게 보면 기본이었다는 생각이 들어 반성합니다ㅜ.ㅜ (pr에도 쓴 아쉬운 점의 현상이 아무래도 동일 항목을 입력했을 때였을 거라고 예상됩니다.) 알려주신 부분 반영해보겠습니다! 감사합니다. |
||||||||||||||||||||||||||||||
event.preventDefault(); | ||||||||||||||||||||||||||||||
const todoInput = document.querySelector(".input").value.trim(); // 입력값 공백 확인 | ||||||||||||||||||||||||||||||
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. 현재 addTodoItem 함수가 실행될때마다 DOM 접근이 되고 있는데, 이렇게 호출하여 추가하는 방법도 좋지만, 조금 더 디테일을 챙기자면 자주 사용되는 DOM 요소는 한번만 선택하고 캐싱해서 사용해 주시면 불필요한 DOM 접근을 줄일 수 있습니다! :)
Suggested change
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. 입력값이 공백일 때의 제한까지 고려하신 점 좋은 것 같아요. 저는 미처 신경쓰지 못한 부분이라 지원님 코드를 본 이후로 더 세심하게 짚고 넘어갈 수 있을 것 같습니다 |
||||||||||||||||||||||||||||||
if (todoInput) { | ||||||||||||||||||||||||||||||
todos.push(todoInput); | ||||||||||||||||||||||||||||||
saveToLocalStorage("todos", todos); // 업데이트된 todos 배열을 localStorage에 저장 | ||||||||||||||||||||||||||||||
printItem(todoInput, "todo"); | ||||||||||||||||||||||||||||||
document.querySelector(".input").value = ""; // 입력창 초기화 | ||||||||||||||||||||||||||||||
updateCounts(); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// 항목 삭제하기 함수 | ||||||||||||||||||||||||||||||
const deleteItem = (e, classSelector, array, key, listSelector) => { | ||||||||||||||||||||||||||||||
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 식별자를 통해서 삭제하는 방식으로 바꾸면 버그를 없앨 수 있을 것 같아요 |
||||||||||||||||||||||||||||||
const target = e.target.closest("li"); | ||||||||||||||||||||||||||||||
const text = target.querySelector(classSelector).innerText; | ||||||||||||||||||||||||||||||
array = array.filter((item) => item !== text); | ||||||||||||||||||||||||||||||
saveToLocalStorage(key, array); | ||||||||||||||||||||||||||||||
document.querySelector(listSelector).removeChild(target); | ||||||||||||||||||||||||||||||
updateCounts(); | ||||||||||||||||||||||||||||||
return array; | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
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. 코드리뷰를 하며 e.target.closest이라는 메서드가 있는지 처음 알게 되었습니다. 삭제 로직이 text로 필터링하여 겹치는 text에 해당하는 item을 삭제하는 로직으로 이해했습니다. 혹여나 text가 겹칠 수 있는 부분도 있고, 함수가 받는 인자가 복잡할 수 있는 부분도 있을 것 같아 고유한 id를 item마다 생성하여 html5 data 속성을 이용하여 고유한 Item을 필터링하는 로직도 참고하시면 좋을 것 같습니다!
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const deleteTodoItem = (e) => { | ||||||||||||||||||||||||||||||
todos = deleteItem(e, ".todo-text", todos, "todos", ".todoList"); | ||||||||||||||||||||||||||||||
updateCounts(); | ||||||||||||||||||||||||||||||
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. updateCounts가 deleteItem 함수 내부에서 이미 호출되고 있으므로, todoToDid 및 didToTodo 함수에서 중복 호출되는 것 같아요. 이러한 중복 호출은 제거해도 괜찮을 것 같습니다 :) |
||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const deleteDidItem = (e) => { | ||||||||||||||||||||||||||||||
dones = deleteItem(e, ".did-text", dones, "dones", ".didList"); | ||||||||||||||||||||||||||||||
updateCounts(); | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// 할 일에서 한 일로 이동 함수 | ||||||||||||||||||||||||||||||
const todoToDid = (e) => { | ||||||||||||||||||||||||||||||
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. 함수 네이밍을 할 때, 함수 이름만으로 동작을 예측할 수 있도록 명확한 역할을 표현하는 이름을 사용하는 것이 좋다고 해요! https://www.youtube.com/watch?v=emGLxi0LvNI&t=556s 또한, 보통 did는 동작을 가리키는 반면에 done은 상태를 가리키는 표현이기 때문에, did보단 done을 사용하는 것이 적절해보여요!! 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 target = e.target.closest("li"); | ||||||||||||||||||||||||||||||
const todoText = target.querySelector(".todo-text").innerText; | ||||||||||||||||||||||||||||||
todos = deleteItem(e, ".todo-text", todos, "todos", ".todoList"); | ||||||||||||||||||||||||||||||
dones.push(todoText); | ||||||||||||||||||||||||||||||
saveToLocalStorage("dones", dones); // 한 일 저장 | ||||||||||||||||||||||||||||||
printItem(todoText, "did"); | ||||||||||||||||||||||||||||||
updateCounts(); | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// 한 일에서 할 일로 이동 함수 | ||||||||||||||||||||||||||||||
const didToTodo = (e) => { | ||||||||||||||||||||||||||||||
const target = e.target.closest("li"); | ||||||||||||||||||||||||||||||
const didText = target.querySelector(".did-text").innerText; | ||||||||||||||||||||||||||||||
dones = deleteItem(e, ".did-text", dones, "dones", ".didList"); | ||||||||||||||||||||||||||||||
todos.push(didText); | ||||||||||||||||||||||||||||||
saveToLocalStorage("todos", todos); // 할 일 저장 | ||||||||||||||||||||||||||||||
printItem(didText, "todo"); | ||||||||||||||||||||||||||||||
updateCounts(); | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
/* todo 입력, 체크, 삭제 */ | ||||||||||||||||||||||||||||||
const form = document.querySelector(".input-box"); // 입력창 폼 요소 | ||||||||||||||||||||||||||||||
const showMessage = document.querySelector(".show-input"); // 입력창 열고 닫는 버튼 요소 | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// 애니메이션 적용한 입력창 열고 닫기 함수 | ||||||||||||||||||||||||||||||
const toggleForm = () => { | ||||||||||||||||||||||||||||||
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. 이런식으로 스타일을 통해서 관리하는 것도 좋지만 입력창의 상태(열림/닫힘)를 명확하게 추적하기 위해 boolean 변수로 상태를 관리하여 여닫는것도 유지보수하기엔 더 편하니까 한번 사용해봐도 좋을 것 같아요 :)_ 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. 클래스명은 워낙 지정하기 나름이라 혼동할 수도 있고, html/css에서 변경될 가능성도 있어 확실히 클래스 변경에 의존하는 것보다는 말씀해주신 것이 훨씬 직관적이고 혼란을 줄일 수 있겠다는 생각이 듭니다! 좋은 의견 주셔서 감사합니다 |
||||||||||||||||||||||||||||||
if (form.classList.contains("show")) { | ||||||||||||||||||||||||||||||
form.classList.remove("show"); | ||||||||||||||||||||||||||||||
form.classList.add("hide"); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
form.addEventListener( | ||||||||||||||||||||||||||||||
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. toggleForm 함수가 실행될 때마다 animationend 이벤트 핸들러가 새로 추가되도록 코드가 작성되어있는 부분을 수정하면 좋을 것 같아요! ddEventListener()의 3번째 파라미터로 { once : true } 전달해 주어서, 이벤트가 발생할 때 한 번만 동작하게 되긴 하지만, 때문에 이 방식보다 { once: true } 옵션을 쓰지 않고, ` // 단순히 클래스만 토글하도록!! const toggleForm = () => { // 이벤트 리스너 초기에 1번만 등록!! 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. 수정방향 코드까지 너무 감사드립니다!! 처음에 클래스 토글까지 구현한 상태에서 애니메이션을 추가하여 한번에 쓰려하다 보니 불필요한 동작이 일어나게 되는 것까지 고려하지 못한 것 같습니다. ㅜㅜ 한 함수에서 담당하는 기능이 최소화되도록 분리하여 쓰는 습관을 길러야겠다는 생각이 많이 듭니다. |
||||||||||||||||||||||||||||||
"animationend", | ||||||||||||||||||||||||||||||
() => { | ||||||||||||||||||||||||||||||
form.style.display = "none"; | ||||||||||||||||||||||||||||||
form.classList.remove("hide"); | ||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||
{ once: true } | ||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||
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. 입력창을 열고 닫을 때 애니메이션을 적용한 디테일 챙기는 부분 정말 좋네요! |
||||||||||||||||||||||||||||||
showMessage.innerHTML = "입력창 불러오기"; | ||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||
form.style.display = "flex"; | ||||||||||||||||||||||||||||||
form.classList.remove("hide"); | ||||||||||||||||||||||||||||||
form.classList.add("show"); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
showMessage.innerHTML = "입력창 다시닫기"; | ||||||||||||||||||||||||||||||
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. animationend라는 이벤트 리스너 타입을 활용하여 UI를 신경 쓴 부분이 인상깊습니다. 저는 처음 본 방법이라 신기하고, JS로 어느정도 수준의 UI까지 활용가능할 수 있는지 호기심이 생겼습니다. |
||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// 이벤트 리스너 등록 및 기존 데이터 불러오기 함수 | ||||||||||||||||||||||||||||||
const init = () => { | ||||||||||||||||||||||||||||||
initTodoList(); | ||||||||||||||||||||||||||||||
updateCounts(); | ||||||||||||||||||||||||||||||
form.addEventListener("submit", addTodoItem); | ||||||||||||||||||||||||||||||
showMessage.addEventListener("click", toggleForm); | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
init(); | ||||||||||||||||||||||||||||||
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. window.onload / document.addEventListener("DOMContentLoaded" / 이벤트 리스너 등록없이 함수 실행 이라는 렌더링 방식에 있어 각각의 사용목적과 장단점이 있음을 이번에 알게 되었습니다. 링크 한번 달겠습니다! 한번 참고하셔도 좋을 것 같습니다. (링크를 줄이는 방법을 몰라 링크가 긴 점 양해부탁드립니다..) |
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.
아이콘과 로고 같은 경우에는 png, jpeg 보다는 SVG 형식을 쓰는게 좋아요! SVG 형식은 벡터 기반이기 때문에 크기가 조절되어도 이미지 품질이 손상되지 않기 때문에 유연한 크기 조절이 가능한 SVG 형식이 아이콘과 로고 같은 작은 이미지에 적합합니다 :)
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.
사실 delete 아이콘이 같은 로직을 통해 생성되는데도 어떤 건 약간 가로로 길쭉한 .. 현상이 있었어서 width, height, 그걸 감싸는 별도의 태그 추가 등 크기 관련 코드만 여러 번 고쳐보았었는데 svg로 바꿔보아야겠다는 생각은 못했네요..! 첨부해주신 표 덕분에 정확히 알아가서 쓸 수 있을 것 같아요☺️