설계
- To Do List
- 저장 문제 > 입력 후 Enter 키를 눌러도 저장이 안 됨
- css 수정
To Do List - 저장 문제
수정 전[js/todo.js]
function handleToDoSubmit(event){
event.preventDefault();
const newTodo = toDoInput.value;
toDoInput.value = "";
const newTodoObj = {
text: newTodo,
id : valTime.now(),
}
toDos.push(newTodoObj);
paintToDo(newTodoObj);
saveToDos();
}
- newTodoObj 안의 “id”의 값인 “valTime.now()”가 문제였음
- “valTime.now()”가 아닌 “Date.now()”가 정상 코드인데, 다른 파일에서 Date라는 키워드를 valTime 으로 전체 수정하면서 해당 파일까지 영향을 미친 것으로 판단
수정 후[js/todo.js]
function handleToDoSubmit(event){
event.preventDefault();
const newTodo = toDoInput.value;
toDoInput.value = "";
const newTodoObj = {
text: newTodo,
id : Date.now(),
}
toDos.push(newTodoObj);
paintToDo(newTodoObj);
saveToDos();
}
- newTodoObj 안의 “id” 값을 “Date.now()” 로 수정
결과
- 화면 상에서도 To Do List 추가 및 삭제된 것이 잘 나온다.
- 로컬 스토리지에도 To Do List와 관련된 키인 “todos”와 그와 연관된 값이 잘 나온다.
To Do List - CSS 수정
결과 화면
- “TO DO LIST” text > 가운데 정렬
- 입력창 및 LIST CSS 수정
소스 코드
#todo-list-text {
display: flex;
justify-content: center;
}
#todo-form input {
background: transparent;
border: 2px solid #f6f6f6;
color: #f6f6f6;
font-family: 'BMHANNAPro';
font-size: 25px;
width: 400px;
border-radius: 40px;
padding-left: 20px;
margin-left: 10px;
}
#todo-form input::placeholder {
color: #f6f6f6;
}
.toDoText {
color: #f6f6f6;
font-family: 'BMHANNAPro';
margin-top: 15px;
margin-left: 20px;
font-size: 20px;
}
- #todo-list-text
- middle DIV의 가운데에 오도록 display의 속성을 flex로, justify-content의 속성을 center로 주었다.
전체 소스 코드
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="css/style.css" />
<title>Irish App</title>
</head>
<body id="background-image">
<div id="container">
<!-- 최상단 => 환영 문구 -->
<div class="top">
<h1>Welcome! This is Irish's Web Page.</h1>
</div>
<!-- 좌측 상단 => 로그인 / 시간 API / 날씨 API -->
<div class="leftTop">
<!-- 로그인 폼 -->
<form action id="login-form">
<input
required
maxlength="15"
type="text"
placeholder="아이디를 입력하세요"
/>
<input type="submit" value="Log In" />
</form>
<!-- <h1 id="greeting" class="hidden"></h1> -->
<div id="divGreeting">
<h1 id="greeting" class="hidden"></h1>
<div id="divLogoutBtn">
<form action id="logoutBtn" class="hidden">
<input type="submit" value="Log Out" />
</form>
</div>
</div>
<!-- <div id="divLogoutBtn">
<form action id="logoutBtn" class="hidden">
<input type="submit" value="Log Out" />
</form>
</div> -->
<!-- 시간 API -->
<div id="divClock">
<h2 id="clock" style="color:orangered">00:00:00</h2>
</div>
<!-- 날씨 API -->
<div id="weather">
<p></p>
<p></p>
<img class="weatherIcon" />
</div>
</div>
<!-- 좌측 하단 => 자주 사용하는 웹 페이지 위젯 -->
<div class="leftBottom">
<div class="wrapBottom">
<a href="https://google.com">
<div class="logo_div logo_google"></div>
</a>
<a href="https://www.youtube.com/">
<div class="logo_div logo_youtube"></div>
</a>
<a href="https://chat.openai.com/">
<div class="logo_div logo_chatgpt"></div>
</a>
<a href="https://www.naver.com/">
<div class="logo_div logo_naver"></div>
</a>
</div>
<div class="wrapBottom">
<a href="https://stackoverflow.com/">
<div class="logo_div logo_stackoverflow"></div>
</a>
<a href="https://developer.mozilla.org/ko/">
<div class="logo_div logo_mdn"></div>
</a>
<a href="https://www.coupang.com/">
<div class="logo_div logo_coupang"></div>
</a>
<a href="https://www.musinsa.com/app/">
<div class="logo_div logo_musinsa"></div>
</a>
</div>
</div>
<!-- 가운데 => TO DO LIST -->
<div class="middle">
<div id="todo-list-text">
<h1>TO DO LIST</h1>
</div>
<div>
<form id="todo-form">
<input type="text" size=30 placeholder="할 일을 적어주세요" required />
</form>
<ul id="todo-list"></ul>
</div>
</div>
<!-- 우측 => 깃허브 / VELOG / 노션 등 프로필 -->
<div class="right">
<h1>Right Area</h1>
</div>
<!-- 최하단 => 유명 문구 -->
<div class="bottom">
<div id="quote">
<span class="fontFaceText"></span> <br>
<span class="fontFaceWriter"></span>
</div>
</div>
</div>
<script src="js/greeting.js"></script>
<script src="js/clock.js"></script>
<script src="js/quotes.js"></script>
<script src="js/todo.js"></script>
<script src="js/background.js"></script>
<script src="js/weather.js"></script>
</body>
</html>
js/todo.js
const toDoForm = document.getElementById("todo-form");
const toDoInput = document.querySelector("#todo-form input");
const toDoList = document.getElementById("todo-list");
const TODOS_KEY = "todos";
// 새로고침 시 로컬 스토리지 값 Binding 위해 const가 아닌 let 활용
let toDos = [];
function saveToDos(){ // toDo 리스트들을 로컬 스토리지에 배열 형식으로 저장
// (STRINGIFY = 변수 등을 문자열로 바꿈, PARSE = 문자열을 JSON으로 바꿈)
localStorage.setItem(TODOS_KEY, JSON.stringify(toDos));
}
function deleteToDo(event){ // 특정 toDo를 삭제하는 함수
const li = event.target.parentElement;
li.remove();
toDos = toDos.filter(toDo => toDo.id !== parseInt(li.id));
saveToDos(); // 한 번 더 저장!
}
function paintToDo(newTodo){ // toDo를 그리는 함수
const li = document.createElement("li");
li.id = newTodo.id;
li.classList.add("toDoText"); // 각 todo에 글자 모양 추가
const span = document.createElement("span");
span.innerText = newTodo.text; // 파라미터인 newTodo가 객체이므로 글자를 보고 싶으면 text 메서드 사용
const button = document.createElement("button");
button.innerText = "X";
button.addEventListener("click", deleteToDo);
li.appendChild(span); // HTML의 li 태그에 span 태그를 자식으로 넣기
li.appendChild(button);
toDoList.appendChild(li);
}
function handleToDoSubmit(event){
event.preventDefault(); // 새로 고침 방지
const newTodo = toDoInput.value;
toDoInput.value = "";
const newTodoObj = { // 객체 느낌으로 로컬 스토리지에 값 넣기
text: newTodo,
id : Date.now(),
}
toDos.push(newTodoObj);
paintToDo(newTodoObj);
saveToDos();
}
toDoForm.addEventListener("submit", handleToDoSubmit);
const savedToDos = localStorage.getItem(TODOS_KEY);
if (savedToDos !== null) {
const parsedToDos = JSON.parse(savedToDos);
// 새로고침시 배열 toDos에 기존 로컬 스토리지 값 넣어주기
toDos = parsedToDos;
parsedToDos.forEach(paintToDo);
}
css/style.css
@import url(//fonts.googleapis.com/earlyaccess/nanumpenscript.css);
@font-face {
font-family: 'BMHANNAPro';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_seven@1.0/BMHANNAPro.woff') format('woff');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'BMYEONSUNG';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_one@1.0/BMYEONSUNG.woff') format('woff');
font-weight: normal;
font-style: normal;
}
body {
margin:0;
padding:0;
font-family: 'BMHANNAPro';
}
#background-image {
background-size: cover;
}
#container {
width:1800px;
margin:auto;
}
.hidden{
display:none;
}
div.top {
background: rgba(255, 255, 255, 0.8);
position: relative;
width: 1800px;
height: 80px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
}
div.leftTop, div.leftBottom{
width: 900px;
height: 340px;
word-break: break-all;
border-radius: 30px;
}
div.leftTop {
position: fixed;
background: rgba(50, 100, 150, 0.8);
top : 85px;
}
div.leftBottom {
position: fixed;
background: rgba(150, 100, 50, 0.8);
top: 425px;
}
#divClock {
margin-left: 50px;
}
div.middle, div.right{
height: 680px;
word-break: break-all;
border-radius: 30px;
}
div.middle {
position: absolute;
background: rgba(112, 112, 112, 0.8);
width: 450px;
margin-left: 900px;
top: 85px;
}
div.right {
position: absolute;
background: rgba(0, 0, 255, 0.8);
width: 450px;
margin-left: 1350px;
top : 85px;
}
div.bottom {
background: rgba(255, 0, 0, 0.8);
position: fixed;
width: 1800px;
top: 770px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
}
span.fontFaceText{
color:black;
font-family: 'Nanum Pen Script', cursive;
font-size: 28pt;
}
span.fontFaceWriter{
color:blue;
font-family: 'Nanum Pen Script', cursive;
font-size: 24pt;
}
div.logo_div {
width: 128px;
height: 128px;
background-size: contain;
display: inline-block;
margin: 20px;
}
div.wrapBottom {
width: 900x;
height: 170px;
margin-left:75px;
}
div.logo_google {
background-image: url(https://cdn1.iconfinder.com/data/icons/google-s-logo/150/Google_Icons-09-256.png);
}
div.logo_youtube {
background-image: url(https://icons.iconarchive.com/icons/dakirby309/simply-styled/128/YouTube-icon.png);
}
div.logo_chatgpt {
background-image: url(https://freelogopng.com/images/all_img/1681038242chatgpt-logo-png.png);
}
div.logo_naver {
background-image: url(https://cdn.icon-icons.com/icons2/652/PNG/128/naver_icon-icons.com_59879.png);
}
div.logo_stackoverflow {
background-image: url(https://cdn-icons-png.flaticon.com/128/2111/2111628.png);
}
div.logo_mdn {
background-image: url(https://iconape.com/wp-content/png_logo_vector/mdn.png);
}
div.logo_coupang {
background-image: url(https://brandlogos.net/wp-content/uploads/2022/04/coupang-logo-brandlogos.net_-512x512.png);
}
div.logo_musinsa {
background-image: url(https://static.msscdn.net/mfile_s01/fb/share_musinsa.png);
}
#todo-list-text {
display: flex;
justify-content: center;
}
#todo-form input {
background: transparent;
border: 2px solid #f6f6f6;
color: #f6f6f6;
font-family: 'BMHANNAPro';
font-size: 25px;
width: 400px;
border-radius: 40px;
padding-left: 20px;
margin-left: 10px;
}
#todo-form input::placeholder {
color: #f6f6f6;
}
.toDoText {
color: #f6f6f6;
font-family: 'BMHANNAPro';
margin-top: 15px;
margin-left: 20px;
font-size: 20px;
}