관리 메뉴

공부기록용

Javascript로 Todo List 만들기(2주) 본문

🗂️프로젝트🗂️

Javascript로 Todo List 만들기(2주)

과부하가즈아 2023. 10. 16. 19:12

🕐2주차 개발일지🕧

📌2주차 목표📌

  • todo작성
    • 아무것도 없는 페이지에 작성하면 해당 css와 내용이 함께 입력
    • 작성일자 함께 입력
  • todo삭제
    • 단일 todo 삭제
      • alert로 삭제 여부 확인하기
    • 모든 todo 일괄삭제
      • alert로 전체 삭제 여부 확인하기
  • CSS추가
    • Animation넣어보기

초반에 배운 animation이랑 keyframes를 다시 사용해보기

h1 {
  text-align: center;

  animation: blink;   
  animation-duration: 3s;
  animation-iteration-count: infinite;
  animation-timing-function: ease-in-out;
  animation-direction: alternate;
}
@keyframes blink {
  0% {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

날짜 구하기

// 날짜구하기
let today = new Date();
let year = today.getFullYear(); // 년도
let month = today.getMonth() + 1; // 월
let date = today.getDate(); // 날짜

//...

dateEl.innerText = `${year}-${month}-${date}`;
// 2023-10-15

 

⬇️⬇️

 

박스 요소를 만들면서 내용과 함께 추가하기

const htmlDiv = document.querySelector(".practice_div"); const htmlBtn = document.querySelector(".practice_btn");

// 다음 코드는 안됨❌❌
htmlBtn.addEventListener("click", () => {
  const divEl = document.createElement("div"); // div요소 만들었고
  const btnEl = document.createElement("button");

  let divElement = htmlDiv
    .appendChild(divEl)
    .setAttribute("class", "container_div");
  // let btnElement = htmlDiv.appendChild(btnEl).setAttribute("class", "container_btn")

  //divElement.innerHTML += '안녕하세요';
  divElement.innerText += "안녕하세요";
  // ➡️ 안녕하세요가 안들어감
});
const htmlDiv = document.querySelector(".practice_div"); const htmlBtn = document.querySelector(".practice_btn");

/* 
📌문제해결📌
setAttribute 메서드는 속성을 설정하고, 해당 요소를 반환하지 않는다.
let divElement = htmlDiv.appendChild(divEl).setAttribute("class", "container_div")는 제대로 작동하지 않음
실제로 요소를 추가한 후에 클래스를 설정하려면 두 가지 방법을 사용해 볼 수 있다.
2️⃣가 원래하려던 방향과 비슷하고, 권장하는 방법은 1️⃣라고 함
*/

// 1️⃣ classList 속성을 사용하여 클래스를 추가하고, 요소를 추가
htmlBtn.addEventListener("click", () => {
  const divEl = document.createElement("div"); // div요소 만들었고
  const btnEl = document.createElement("button");

  divEl.classList.add("container_div");
  btnEl.classList.add("container_btn");

  divEl.innerText = "안녕하세요";
  btnEl.textContent = "누르기";

  htmlDiv.appendChild(divEl);
  htmlDiv.appendChild(btnEl);
});


// 2️⃣ appendChild 메서드를 사용한 후 클래스 설정
htmlBtn.addEventListener("click", () => {
  const divEl = document.createElement("div");
  const btnEl = document.createElement("button");

  // divEl를 htmlDiv에 추가
  htmlDiv.appendChild(divEl);
  htmlDiv.appendChild(btnEl);

  // 추가한 후에 클래스 설정
  divEl.setAttribute("class", "container_div");
  btnEl.setAttribute("class", "container_btn");

  // 텍스트 추가
  divEl.textContent = "안녕하세요";
  btnEl.innerText = "누르기"
});

입력 창의 값들 입력과 동시에 없애기

const titleInputVal = titleInput.value; // 할일값
const memoInputVal = memoInput.value; // 메모값

// 값을 초기화가 아니고 요소를 초기화니까!! 이게 아니고
titleInputVal.value = "";
memoInputVal.value = "";

// 이거!!
titleInput.value = "";
memoInput.value = "";

/*
titleInput와 memoInput는 실제로 입력 필드를 나타내는 변수이며, 이 변수들은 .value 속성을 갖는다. 
그래서 이것들을 지울 때 titleInput.value = "" 나 memoInput.value = ""로 작성해야 함

titleInputVal과 memoInputVal은 이미 titleInput와 memoInput에서 값을 가져올때 사용된 변수로, 
이러한 변수에 .value 속성을 할당할 필요가 없다. 
대신 titleInput와 memoInput의 값을 직접 빈 문자열로 설정하면 입력 필드의 내용이 지워진다.

titleInputVal = "";와 memoInputVal = "";은 이 변수들을 빈 문자열로 설정할 뿐, 
실제 입력 필드(titleInput 및 memoInput)의 값은 변경하지 않는다.
*/

 

⬇️⬇️

 

단일 todo 삭제

// trash btn
btnEl.classList.add("element-delete");
btnEl.textContent = "🗑️";
divEl_itemTop.appendChild(btnEl);

// todo 단일 삭제 ✔️
btnEl.addEventListener("click", () => {
  if (confirm("정말 삭제하시겠습니까?")) {
    todoListBox.removeChild(divEl_item);
  }
});

일괄 todo 삭제

// todo 일괄 삭제 ✔️
btnAll.addEventListener("click", () => {
  if (todoListBox.parentNode) {
    todoListBox.parentNode.removeChild(todoListBox);

    /* 삭제와 동시에 페이지 다시 로딩
       item들만 지우고 싶은데 list가 지워져서 바로 재입력이 안되서 
       재부팅을 선택 */
    location.reload(); 
  }
});

 

✍️회고✍️

박스 형식의 todo를 만들고 안의 여러 요소들을 넣으려고 하다보니 입력과 동시에 요소들이 만들어지면서 생성되는 것을 구현하는게 복잡했던것 같다. 그래서 전체 파일을 보면 복잡해져셔 완성 후에 꼭 리팩토링을 해야겠다 싶다. 요소들을 생성하는 방식 말고 다른 방법이 있는데 일단 복잡하지만 쉬운 방법을 택한것 같다. 그래도 덕분에 node나 child, parent들을 좀 자세하게 알게 된 것 같고, target도 좀 자세하게 뜯어보는 계기가 되어서 얻는게 있어 좋은 경험이었다. 

 

그리고 confirm을 활용하는 방법도 알게 되었다. 정말 단순한 구현이지만 나름 중요한 요소라고 생각했던 기능인데 사용해 볼 수 있어서 좋았다. 다만 아직 전체 todo를 삭제시에도 사용해보고 싶은데 요소하나하나를 확인하느라 confirm이 여러번 작동해서 이건 다시 도전을 해봐야한다.

 

그래도 우선 목표했던 주요 기능을 구현함에 있어서 아직 많이 부족하지만 그래도 구현해냈다는 것에 뿌듯하기도 하고 목표한 구현에 조금씩 가까워지는 것 같아서 재밌는 주간이었다. 

📌3주차 목표📌

  • todo 수정하기
    • title 수정
    • memo 수정
  • 진행중과 완료의 분류
    • 진행 중인 todo카운팅
  • 드래그 앤 드롭 구현
    • 진행 중과 완료로의 전환도 같이 구현

🤔아쉬운 점🤔

코드가 너무 지저분하다! 기능 단위로의 분류가 필요하다! 미숙함이 이런 곳에서 확실히 드러나는 것 같다! 리팩토링이 시급하다! B.E가 없어서 todo가 다 날아간다,,,ㅎ

 

index.html
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>STUDY 01</title>
    <link rel="stylesheet" href="style.css" />
    <style>
      @import url('https://fonts.googleapis.com/css2?family=Do+Hyeon&display=swap');
    </style>
  </head>
  <body>
    <h1>🐰 TO DO LIST 🥕</h1>
    <!-- todo입력 구현 -->
    <div class="todo-wrapper">
      <!-- 할일입력 -->
      <div class="todo-input-box">
        <div class="title-input-box">
          <label class="input-title">Title</label>
          <input class="todo-title-input" type="text" placeholder="할 일을 입력하세요." maxlength=15/>
        </div>
               
        <div class="memo-input-box">
          <label class="input-title">Memo</label>
          <input class="todo-memo-input" type="text" placeholder="20자 내외로 작성하세요." maxlength=20></input>
        </div>
        
        <button class="todo-input-btn">입력</button>
      </div>

      <!-- 할일출력 -->
      <div class="todo-list">
        <!-- <div class="todo-item">

          <div class="todo-item-top">
            <div class="todo-create-date"></div> 
            <button class="element-delete">🗑️</button>
          </div>

          <div class="todo-title"></div>
          <div class="todo-memo"></div> 

        </div> -->
      </div>

      <!-- 부가기능 -->
      <div class="footer">
        <button class="all-delete">전체 삭제</button>
      </div>
    </div>

    <script src="index.js"></script>
  </body>
</html>
style.css
* {
  font-family: "Do Hyeon", sans-serif;
}
body {
  background-color: rgb(231, 247, 235);
}

h1 {
  text-align: center;

  animation: blink;   
  animation-duration: 3s;
  animation-iteration-count: infinite;
  animation-timing-function: ease-in-out;
  animation-direction: alternate;
}
@keyframes blink {
  0% {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

.todo-input-box {
  margin: 50px auto 40px;
  display: flex;
  justify-content: center;
  gap: 20px;
}
.todo-input-btn {
  cursor: pointer;
}
.todo-list {
  margin: 0px auto;
  display: flex;
  /* 자식요소들간의 간격 */
  gap: 15px;
  /* 중앙정렬 */
  justify-content: center;
  /* align-content는 flex-wrap와 같이 써야 되고
  요소와 요소사이의 간격을 가운데를 기준으로의 설정을 
  믜미한다. */
  flex-wrap: wrap;
  align-content: space-around;

  height: auto;
}
.todo-item {
  border: 3px solid rgb(221, 111, 8);
  border-radius: 12px;
  padding: 12px 20px 15px;
  width: 270px;
  /* height: 210px; */
}
.todo-item-top {
  display: flex;
}
.todo-create-date {
  width: 50%;
  font-size: 12px;
}
.element-delete {
  border: none;
  border-radius: 8px;
  /* 배경을 투명하게 */
  background-color: transparent;
  /* opacity: 0; -> 전체 투명도 조절(내용까지) */
  margin-left: 50%;
  cursor: pointer;
}

.todo-title {
  font-weight: bold;
  /* 테두리는 삭제 예정 */
  border: 2px solid rgb(129, 189, 137);
  border-radius: 10px;

  width: 250px;
  height: 20px;
  margin: 20px 5px;

  /* 내부 텍스트의 위치를 조정 */
  padding: 5px 5px;
}

.todo-memo {
  border: 2px solid rgb(129, 189, 137);
  border-radius: 10px;

  margin: 0px 4px;
  width: 240px;
  height: 100px;

  /* 내부 텍스트의 위치 조정 */
  padding: 5px 10px;
}

.footer {
  margin: 30px auto;
  padding: auto;
  display: flex;
  justify-content: center;
}
.all-delete {
  cursor: pointer;
}
index.js
const titleInput = document.querySelector(".todo-title-input"); // 할일입력input
const memoInput = document.querySelector(".todo-memo-input"); // 메모입력input
const todoBtn = document.querySelector(".todo-input-btn"); // 입력btn

const title = document.querySelector(".todo-title"); // 할일나오는div
const memo = document.querySelector(".todo-memo"); // 메모나오는div

// todo입력
function createTodo(e) {
  e.preventDefault();

  const todoListBox = document.querySelector(".todo-list"); // todo나오는 전체공간
  const todoItemBox = document.querySelector(".todo-item"); // todo하나하나

  // 날짜구하기
  let today = new Date();
  let year = today.getFullYear(); // 년도
  let month = today.getMonth() + 1; // 월
  let date = today.getDate(); // 날짜

  const titleInputVal = titleInput.value; // 할일값
  const memoInputVal = memoInput.value; // 메모값

  // 삭제 구현시 필요
  const wrapper = document.querySelector(".todo-wrapper"); // 전체 warpper div
  const btnAll = document.querySelector(".all-delete"); // 전체 삭제btn

  // 요소 만들기
  const divEl_item = document.createElement("div"); // todo-item
  const divEl_itemTop = document.createElement("div"); // todo-item-top
  const dateEl = document.createElement("div"); // date
  const btnEl = document.createElement("dutton"); // element-del
  const divEl_title = document.createElement("div"); // title
  const divEl_memo = document.createElement("div"); // memo

  if (titleInputVal === "" && memoInputVal === "") {
    alert("TODO를 입력하세요.");
  } else if (titleInputVal === "") {
    alert("할 일을 입력하세요.");
  } else if (memoInputVal === "") {
    alert("메모를 남겨주세요");
  } else {
    // item box
    divEl_item.classList.add("todo-item");
    todoListBox.appendChild(divEl_item);

    // todo 일괄 삭제 ✔️
    btnAll.addEventListener("click", () => {
      if (todoListBox.parentNode) {
        todoListBox.parentNode.removeChild(todoListBox);

        // 삭제와 동시에 페이지 다시 로딩
        // list가 지워져서 새로 입력이 안되서 item들만 지우고 싶은데 복잡해져서
        // 그냥 재부팅을 선택
        location.reload(); 
      }
    });

    // item bot top
    divEl_itemTop.classList.add("todo-item-top");
    divEl_item.appendChild(divEl_itemTop);

    // date
    dateEl.classList.add("todo-create-date");
    dateEl.innerText = `${year}-${month}-${date}`;
    divEl_itemTop.appendChild(dateEl);

    // trash btn
    btnEl.classList.add("element-delete");
    btnEl.textContent = "🗑️";
    divEl_itemTop.appendChild(btnEl);

    // todo 단일 삭제 ✔️
    btnEl.addEventListener("click", () => {
      if (confirm("정말 삭제하시겠습니까?")) {
        todoListBox.removeChild(divEl_item);
      }
    });

    // title
    divEl_title.classList.add("todo-title");
    divEl_title.innerText = titleInputVal;
    divEl_item.appendChild(divEl_title);

    // memo
    divEl_memo.classList.add("todo-memo");
    divEl_memo.innerText = memoInputVal;
    divEl_item.appendChild(divEl_memo);

    // 입력창 초기화
    titleInput.value = "";
    memoInput.value = "";
  }
}

// 입력 btn
todoBtn.addEventListener("click", createTodo);
Comments