-
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주차] 윤영준 미션 제출합니다. #2
base: master
Are you sure you want to change the base?
Changes from all commits
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,53 @@ | |
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Vanilla Todo</title> | ||
<title>To Do List-yyj0917</title> | ||
<link rel="stylesheet" href="style.css" /> | ||
</head> | ||
|
||
<body> | ||
<div class="container"></div> | ||
<div class="wrapper"> | ||
<div class="container"> | ||
<!-- header --> | ||
<header> | ||
<h2 class="today"></h2> | ||
<span> | ||
<h1>Today</h1> | ||
<input type="date" id="date-picker" class="date-picker"> | ||
</span> | ||
</header> | ||
<!-- nav --> | ||
<nav> | ||
<ul class="week-days"></ul> | ||
</nav> | ||
<!-- main --> | ||
<main class="task-list"></main> | ||
<!-- footer --> | ||
<footer> | ||
<button class="btn-modal">+</button> | ||
<form class="task-form" style="display: none;"> | ||
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. 나타났다, 사라졌다 하는 요소의 |
||
<div> | ||
<input | ||
type="text" | ||
id="task-title" | ||
placeholder="Task Title" | ||
required | ||
/> | ||
<input | ||
type="text" | ||
id="task-desc" | ||
placeholder="Task Description" | ||
required | ||
/> | ||
</div> | ||
<div> | ||
<button type="submit" class="btn save">save</button> | ||
<button type="button" class="btn back">back</button> | ||
Comment on lines
+46
to
+47
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. form 태그 내의 button을 저는 기본 타입인 submit으로만 사용했어서 간과하고 있었는데, 덕분에 type에 관해 알아보게 되어서 적극 활용해야겠다는 생각이 듭니다 😂 |
||
</div> | ||
</form> | ||
Comment on lines
+30
to
+49
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 요소의 form 시스템을 잘 활용하고 계신 것 같아요! |
||
</footer> | ||
</div> | ||
</div> | ||
</body> | ||
<script src="script.js"></script> | ||
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 문서가 parsing 되는 시점을 고려하신 것 같습니다. 위의 body 태그 내부에 있는 DOM 요소들이 모두 load 된 후 js로 관련된 작업을 하기 위해서 script 태그를 뒤에 배치하셨군요! 이 방식도 정말 좋고, script 요소에 defer 속성을 주시면 그냥 편하게 head 태그에 삽입하셔도 될 거에요 :) 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파싱과 스크립트 로드의 동작과 관련하여 더 공부하게 되었습니다. async, defer 속성을 잘 활용한다면 번들 사이즈가 큰 코드에서도 효율적으로 로딩 시간을 관리할 수 있겠군요! 감사합니다. |
||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,206 @@ | ||
//😍CEOS 20기 프론트엔드 파이팅😍 | ||
|
||
// DOM Load시 실행되는 이벤트 리스너 | ||
document.addEventListener('DOMContentLoaded', function () { | ||
const currentDate = new Date(); | ||
const today = document.querySelector('.today'); | ||
const weekDays = document.querySelector('.week-days'); | ||
const taskList = document.querySelector('.task-list'); | ||
const modalBtn = document.querySelector('.btn-modal'); | ||
const taskAddForm = document.querySelector('.task-form'); | ||
const backBtn = document.querySelector('.back'); | ||
const datePicker = document.getElementById('date-picker'); | ||
const ulElement = document.querySelector('.week-days'); | ||
|
||
|
||
function updateDisplayDate(date) { | ||
today.innerText = date.toLocaleString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); | ||
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. date 인스턴스의 메서드를 잘 활용하셔서 알맞은 포맷의 데이터를 사용자에게 UI로 제공하는 모습 👍 |
||
} | ||
|
||
// input type=date의 초기값 설정 | ||
function getFormattedDate(date) { | ||
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. js 과제라 어쩔 수 없긴 한데,,,, date 인자에 타입스크립트 적용하고 싶은 욕구가 드네요 😙 |
||
const year = date.getFullYear(); | ||
const month = ('0' + (date.getMonth() + 1)).slice(-2); | ||
const day = ('0' + date.getDate()).slice(-2); | ||
Comment on lines
+23
to
+24
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 `${year}-${month}-${day}`; | ||
} | ||
|
||
let selectedDate = getFormattedDate(currentDate); // selectedDate 초기값은 오늘 날짜 | ||
|
||
// 상단 날짜 업데이트 함수 | ||
|
||
// input의 초기 값을 오늘 날짜로 설정 | ||
datePicker.value = selectedDate; | ||
|
||
// 날짜 선택 이벤트 리스너 | ||
datePicker.addEventListener('change', function(event) { | ||
const selectedDateObj = new Date(event.target.value); // 사용자가 선택한 날짜 | ||
selectedDate = selectedDateObj.toISOString().split('T')[0]; // 선택된 날짜의 'YYYY-MM-DD' 형식으로 설정 | ||
updateDisplayDate(selectedDateObj); // 상단 날짜 업데이트 | ||
loadWeekDays(selectedDateObj); // 선택한 날짜의 주간 표시 | ||
loadTasks(selectedDate); // 선택된 날짜의 할 일 불러오기 | ||
}); | ||
|
||
|
||
|
||
|
||
// 일주일 날짜 생성, 오늘 날짜 표시 | ||
function loadWeekDays(selectedDateObj = currentDate) { | ||
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. 함수의 인자로 default value를 적극적으로 활용해주셔서 처음 DOM이 로드될 때의 상황까지 잘 커버해주신 것 같습니다 |
||
weekDays.innerHTML = ''; | ||
updateDisplayDate(selectedDateObj); | ||
|
||
// 날짜 계산 주간 첫번째 날 웡요일 설정로직 | ||
const firstDayOfWeek = new Date(selectedDateObj); | ||
const dayOfWeek = selectedDateObj.getDay(); | ||
|
||
// 일요일인 경우 특별 처리 | ||
if (dayOfWeek === 0) { | ||
firstDayOfWeek.setDate(selectedDateObj.getDate() - 6); // 일요일일 경우 전주의 월요일을 설정 | ||
} else { | ||
firstDayOfWeek.setDate(selectedDateObj.getDate() - dayOfWeek + 1); // 월요일 설정 | ||
} | ||
|
||
|
||
for (let i = 0; i < 7; i++) { | ||
const day = new Date(firstDayOfWeek); | ||
day.setDate(firstDayOfWeek.getDate() + i); | ||
const dayElement = document.createElement('li'); | ||
const dateKey = day.toISOString().split('T')[0]; | ||
|
||
// week set | ||
const weekSpan = document.createElement('span'); | ||
weekSpan.className = 'week'; | ||
weekSpan.innerText = day.toLocaleString('en-US', { weekday: 'short' }); | ||
|
||
// day set | ||
const daySpan = document.createElement('span'); | ||
daySpan.className = 'day'; | ||
daySpan.innerText = day.getDate(); | ||
|
||
// dateKey 설정 | ||
dayElement.dataset.dateKey = dateKey; | ||
|
||
// Today selected set | ||
if (dateKey === selectedDateObj.toISOString().split('T')[0]) { | ||
dayElement.classList.add('selected'); | ||
loadTasks(selectedDate); | ||
} | ||
|
||
// 개별 스타일을 위한 span tag 삽입 | ||
dayElement.appendChild(weekSpan); | ||
dayElement.appendChild(daySpan); | ||
weekDays.appendChild(dayElement); | ||
} | ||
// UL 요소에 이벤트 리스너 등록 (이벤트 위임) | ||
ulElement.addEventListener('click', (event) => { | ||
const dayElement = event.target.closest('li'); // 클릭된 요소 중 가장 가까운 li 요소 찾기 | ||
if (dayElement) { // li 요소가 클릭된 경우만 실행 | ||
const dateKey = dayElement.dataset.dateKey; // li의 데이터 속성에서 dateKey 가져오기 | ||
selectDate(dayElement); // 날짜 선택 처리 | ||
selectedDate = dateKey; // 선택된 날짜 설정 | ||
datePicker.value = dateKey; // 날짜 선택기를 선택한 날짜로 설정 | ||
loadTasks(dateKey); // 선택한 날짜의 할 일 로드 | ||
} | ||
}); | ||
} | ||
// 선택된 날짜 표시 | ||
function selectDate(dayElement) { | ||
const selected = document.querySelector('.selected'); | ||
if (selected) { | ||
selected.classList.remove('selected'); | ||
} | ||
dayElement.classList.add('selected'); | ||
} | ||
|
||
// load To Do List | ||
function loadTasks(dateKey) { | ||
taskList.innerHTML = ''; // 기존 할 일 목록 지우기 | ||
const tasks = JSON.parse(localStorage.getItem(dateKey)) || []; // LocalStorage에서 해당 날짜의 할 일 가져오기 | ||
|
||
tasks.forEach((task, index) => { | ||
addTaskToDOM(task, index); | ||
}); | ||
} | ||
|
||
|
||
// Add To Do List -> DOM에 추가 -> 화면에 띄우기용 | ||
function addTaskToDOM(task, index) { | ||
const taskElement = document.createElement('article'); | ||
taskElement.className = 'task-item'; | ||
taskElement.innerHTML = ` | ||
<div class="task-detail"> | ||
<span class="task-time">${task.time}</span> | ||
<h3 class="task-title">${task.title}</h3> | ||
<p class="task-desc">${task.desc}</p> | ||
<button class="task-delete" data-index="${index}">delete</button> | ||
</div> | ||
`; | ||
Comment on lines
+130
to
+137
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를 innerHTML 문자열로 삽입해주셨네요! 그 과정에서 문자열 리터럴에 변수도 잘 활용해주셨구요. 하지만 innerHTML 방식은 XSS(cross site scripting) 공격에 취약할 수 있다고 해요. 이 방식 말고도 다소 코드가 길어질 여지는 있지만, createElement로 요소들을 더 만든 뒤, 물론 innerHTML은 기존의 DOM 요소를 갈아끼우는 방식으로, 제가 추천드린 방법은 기존의 요소에 추가하는 방식으로 동작하여 관련된 고려가 더 필요할 수도 있어요. 찾아보니 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. XSS 공격을 고려했을 때 충분히 문제가 생길 수 있음을 알려주셔서 감사합니다! 요소에 추가하는 부분과 대체하는 부분이 있을 수 있으니 그 부분을 고려하면서 한번 수정해보겠습니다. 알려주신 내용을 공부하다 textContent vs innerText의 차이도 알게 되었습니다. 파생되는 지식들이 재미를 더 붙여주는 것 같아요! |
||
taskElement.querySelector('.task-delete').addEventListener('click', function () { | ||
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에 요소를 넣어주시고, 그 다음 이벤트 핸들러를 달아주신 것 좋습니다. 제가 QA를 위해 동일한 내용의 todo를 배열에 중복으로 넣고 삭제를 했을 때 알맞은 것이 삭제되나 확인해봤는데, index 값을 처음 DOM에 추가될 때 잘 가지고 있어 제대로 동작하는군요! 이 부분까지 고려하시는 것 너무 좋았던거 같아요. React는 이런 것을 자체적으로 해주므로 너무 편한 기술인 것을 알 수 있는 것 같아요 💪🏼 |
||
const confirm = window.confirm('삭제하시겠습니까?'); | ||
if (confirm) { | ||
deleteTask(selectedDate, index); // 삭제 버튼 클릭 시 할 일 삭제 | ||
} | ||
}); | ||
taskList.appendChild(taskElement); | ||
} | ||
|
||
// Add Task -> LocalStorage에 추가 | ||
function addTaskToLocal(dateKey, task) { | ||
console.log(dateKey); | ||
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 tasks = JSON.parse(localStorage.getItem(dateKey)) || []; | ||
|
||
// 작성시간 | ||
task.createdTime = new Date().toLocaleString('en-US', { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' }); | ||
|
||
tasks.push(task); | ||
localStorage.setItem(dateKey, JSON.stringify(tasks)); | ||
} | ||
|
||
// Delete Task -> LocalStorage에서 삭제 -> 기존 목록 불러오고, 목록에서 삭제 후 삭제된 목록 저장...흠 | ||
function deleteTask(dateKey, taskIndex) { | ||
const tasks = JSON.parse(localStorage.getItem(dateKey)) || []; | ||
tasks.splice(taskIndex, 1); | ||
localStorage.setItem(dateKey, JSON.stringify(tasks)); | ||
loadTasks(dateKey); | ||
Comment on lines
+161
to
+164
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. tasks에 할당된 |
||
} | ||
|
||
// Modal Button | ||
modalBtn.addEventListener('click', () => { | ||
modalBtn.style.display = 'none'; | ||
taskAddForm.style.display = 'flex'; | ||
}); | ||
|
||
// back navigation | ||
backBtn.addEventListener('click', () => { | ||
modalBtn.style.display = 'inline-block'; | ||
taskAddForm.style.display = 'none'; | ||
}); | ||
|
||
// Save Task -> submit 이벤트가 폼을 자동으로 제출하면서 페이지가 새로고침됨 -> 그렇기 때문에 save button이 아닌 | ||
// form에 이벤트리스너를 등록해서 기본동작을 막아야 함. | ||
taskAddForm.addEventListener('submit', function (event) { | ||
event.preventDefault(); | ||
|
||
const taskTitle = document.getElementById('task-title').value.trim(); | ||
const taskDesc = document.getElementById('task-desc').value.trim(); | ||
|
||
const newTask = { | ||
title: taskTitle, | ||
desc: taskDesc, | ||
time: new Date().toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', second: 'numeric' }) | ||
}; | ||
|
||
// input field init 모든 폼 요소를 초기화할 때 사용가능 | ||
taskAddForm.reset(); | ||
|
||
|
||
addTaskToLocal(selectedDate, newTask); | ||
loadTasks(selectedDate); | ||
modalBtn.style.display = 'block'; | ||
taskAddForm.style.display = 'none'; | ||
}); | ||
|
||
loadWeekDays(currentDate); | ||
loadTasks(selectedDate); | ||
|
||
}); |
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.
주석으로 시맨틱 태그를 구분해주셔서 구조가 한 눈에 들어오니 읽기에도 편하네요👍 사소한 습관이지만 좋은 것 같습니다!