상세 컨텐츠

본문 제목

[개인 프로젝트] Vanilla JS로 To Do List 만들기 (2)

Studying (Review)/JavaScript

by 잼(JAM) 2022. 8. 29. 23:34

본문

반응형

지난 시간에 이어 이번에는 JavaScript 코드 작성에 대한 이야기로

포스팅을 이어보려고 한다.

 

작성된 코드에 대한 설명이 주된 내용이므로,
관심이 있거나 필요한 부분만 골라서 보면 될 것 같다.
(혹시나 포스팅 내용에 오류가 있다면 제발 꼭 좀 알려줬으면 좋겠다..)

 


1. 뼈대를 완성하자 (HTML  마크업)

<!DOCTYPE html>
<html lang="ko">

  <head>
    <meta charset="utf-8" />
    <title>To Do List</title>
    <meta name="description" content="바닐라 JS로 만드는 To Do List 웹 애플리케이션" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="index.css">
  </head>
  
  <body>
    <header>
      <h1>To Do List</h1>
    </header>
    <section>
      <form class="input-to-do">
        <input type="text" placeholder="할 일을 입력해주세요!" />
        <button type="submit">입력</button>
      </form>
      <article>
        <h2>What I Have To Do?</h2>
        <div>
          <ul class="what-to-do">
          </ul>
        </div>
      </article>
    </section>
    <footer>
      <div>COPYRIGHT 2022. JAENY, All rights reserved</div>
    </footer>
    <script src="./ToDoList.js"></script>
  </body>

</html>

 

가장 집중한 부분은 semantic 태그를 최대한 잘 사용해보자 하는 것이었고,

기본적으로 필요한 태그나 내용을 최대한 빠뜨리지 말자는 것이었다.

 

아래 내용을 펼쳐서 확인하면, 각 태그 사용에 대한 의도를 간략하게 서술하였다.

 

<head> 태그 내부

더보기

<meta charset="utf-8" />

  • HTML 문자열 해독을 UTF-8 코드셋으로 지정

<title>

  • 브라우저 탭에 들어갈 타이틀 지정

<meta name="description" content="...">

  • 웹 애플리케이션에 대한 기본설명추가 (robot 등을 위한 편의성 semantic 태그??)

<meta name="viewport" content="width=device-width, initial-scale=1.0" />

  • 웹 페이지 화면을 각 기기별 화면의 너비에 맞게 설정해줌, 기본 배율은 1.0으로 지정

<link rel="stylesheet" href="index.css">

  • 웹 애플리케이션의 스타일 지정을 위한 css 파일 import

<body> 태그 내부

더보기

<header>

  • 웹 페이지 최상단부 제목 등의 영역을 위한 semantic 태그 (wrapper)

<h1>

  • 웹 애플리케이션의 목적을 담은 타이틀 지정 (semantic title)

<section>

  • 웹 페이지의 주요한 내용을 담은 semantic 태그 (wrapper)

<form>

  • input을 통해 입력된 value를 submit 하기 위한 logical + semantic 태그

<input type="text" placeholder="할 일을 입력해주세요!">

  • input을 통해 입력할 수 있는 값을 text 형식으로 지정,
    placeholder을 통해 어떤 값을 입력해야 하는지 미리 안내함

<button type="submit">

  • 버튼의 타입을 submit으로 지정하여, 클릭시 form submit 이벤트가 실행되도록 함

<article>

  • section 내부의 주요한 내용을 담은 semantic 태그 (wrapper)

<h2>

  • section 내부의 주요한 내용에 대한 목적을 담은 타이틀 지정 (semantic title),
    중요도는 웹 애플리케이션의 총 타이틀 다음으로 하기위해 h2를 사용

article 내부 <div>

  • article 내부의 컨텐츠를 담기위한 wrapper 태그

<ul>

  • input value의 값을 통해 형성되는 to do list를 담기위한 semantic wrapper
    하지만 할 일의 순서가 중요하지 않기 때문에 ol(ordered list)이 아닌, ul(unordered list)을 사용

<footer>

  • 웹 페이지 하단부 중요하진 않지만, 필수적인 내용을 담은 semantic 태그 (wrapper)

footer 내부 <div>

  • footer에 들어갈 내용을 담은 wrapper 태그

<script src="./ToDoList.js">

  • 웹 애플리케이션의 동작을 위한 script 구성 파일을 담고 있는 태그

2. 기본적인 기능을 추가해보자! (JavaScript로 로직 만들기)

/** 
 * QuerySelect for input Form eventListener
 * @type {Element}
 */ 
const FormToDo = document.querySelector(".input-to-do");

/** 
 * QuerySelect for input eventListener
 * @type {Element}
 */
const InputToDo = FormToDo.querySelector("input");

/** 
 * QuerySelect for ul to paint Element eventListener
 * @type {Element}
 */
const WhatToDo = document.querySelector(".what-to-do");


/** 
 * sessionStorage data keyName
 * @type {string}
 */
const ToDoList = "toDoList";

/** 
 * sessionStorage data valueName
 * @type {Array}
 */
let toDoList = [];

1) 변수 선언

필요한 변수를 const 혹은 재할당 가능한 let 키워드 통해 선언해주었다.

아래 내용을 펼쳐서 확인하면, 각 변수에 대한 의도를 간략하게 서술하였다.

더보기

 

const FormToDo = document.querySelector(".input-to-do");

  • form의 submit을 위한 EventListener를 할당하기 위해 먼저 form에 대한 변수를 선언하였다.

const InputToDo = FormToDo.querySelector("input");

  • input에 대한 value값을 받아오기 위해 input에 대한 변수를 선언하였다

const WhatToDo = document.querySelector(".what-to-do");

  • input에서 받아온 value를 통해 할 일을 list로 추가하기 위해 ul 태그에 대한 변수를 선언하였다
    DOM 조작을 통해 ul 내부에 li 태그 등을 이용하여 화면을 그려주기 위함

const ToDoList = "toDoList";

  • 단순히 동작만 하게 하려면 필요없지만, 새로고침을 했을 때 기존에 작성한 내용이 날아가지 않도록
    sessionStorage를 활용하기 위해, 해당 storage에 사용할 key값에 대한 변수를 선언하였다.

let toDoList = [];

  • 위의 변수가 sessionStorage의 key였다면, 이 변수는 value값을 받기 위해
    재할당이 가능한 let을 통해 선언해주었다
    또한 여러가지의 항목을 받기 위해 Array 데이터 타입으로 받도록 설정하였다
    (+a로 JSON 형식으로 사용하기 위함)

 

2) 함수 선언

동작을 위한 기능을 선언하기 위해 화살표 함수 문법을 사용하여, 각 기능별 함수를 선언 및 할당하였다.
차례대로 내용과 의도를 서술하도록 하겠다. (접힌 글을 펼쳐서 확인!)

/** 
 * create 함수: input의 이벤트처리
 * @param {Event} event submit 이벤트
 */
const createToDo = (event) => {
  event.preventDefault();
  const toDo = InputToDo.value;
  paintToDo(toDo);
  setToDo(toDo);
  InputToDo.value = "";
};

2-1. create(생성) 기능을 위한 함수

더보기

form을 통해 input value가 submit 되었을 때,
새로고침이 되면서 이벤트 발생이 무시되는 것을 막기 위해
preventDefault 함수를 먼저 실행시켜주었다
이후 상수 toDo를 선언하여 value값을 받고,
화면을 실질적으로 그려주는 함수인 paintToDo에 toDo를 인자로 넘겨서
화면에 value 값을 통해 출력을 시켜주고
sessionStorage에 데이터를 저장해주는 setToDo 함수에 toDo를 넘겨주어서
submit을 함으로 sessionStorage에 해당 데이터가 남아있도록 처리해준다.
이 후 input의 값을 빈 값으로 만들어서 초기화를 시켜준다.


/** 
 * paint 함수: 실질적으로 화면에 그리기
 * @param {String} toDo input에 넣은 text(할 일)
 */
const paintToDo = (toDo) => {
  const li = document.createElement("li");
  const input = document.createElement("input");
  input.setAttribute("type", "checkbox");
  input.addEventListener("click", checkToDo)
  const label = document.createElement("label");
  label.innerHTML = toDo;
  const button = document.createElement("button");
  button.addEventListener("click", deleteToDo)
  button.innerHTML = "삭제";
  li.appendChild(input);
  li.appendChild(label);
  li.appendChild(button);
  li.id = toDoList.length + 1;
  WhatToDo.appendChild(li);
}

2-2. paint(화면출력) 기능을 위한 함수

더보기

toDo 즉 input value 값을 인자로 받아 동작하는 함수로서
HTML 문서의 ul 태그 내부에 li, input, label, button 태그를 통해 화면 출력 기능을 담당한다

먼저 li 태그를 생성하는 함수를 li 상수에 할당해주고,
checkbox를 생성하기 위해 input 태그를 생성하는 함수를 input에 할당해준다.
이 후 input 태그의 type 속성을 checkbox로 지정하기 위한 setAttribute 함수를 실행해주고,
checkbox의 체크여부에 따라 EventListener를 활용하기 위해 addEventListener를 통해
click에 대한 Event를 checkToDo 함수에 할당해준다
이 후 input value를 목록으로 그려줄 label 태그를 생성하는 함수를 label 상수에 할당하고,
label의 내용을 input value값으로 채워주기 위해 innerHTML을 통해 input value인
toDo를 label의 내용으로 넣어주도록 한다.

마지막으로 delete 기능을 위한 button 태그를 생성하는 함수를 button 상수에 할당하고,
button의 클릭을 통해 delete 기능을 동작하기 위해 addEventListener를 통해
click에 대한 Event를 deleteToDo 함수에 할당해준다
그리고 button의 기능을 확실히 명시하기 위해
button의 내용을 innerHTML 함수를 통해 "삭제" 라고 명시해주었다.
이후 해당 내용들을 담기 위해 각 태그들을 li 태그 내부의 child로 넣어주기 위해
appendChild 함수를 사용해 각 태그들을 child로 지정하여 넣어주고
li 태그의 삭제 기능 활용을 위해 id값을 toDoList.length를 기준으로 추가해준다.
해당하는 li 태그를 최종적으로 ul 태그인 WhatToDo 상수의 child로 생성하도록 하여
함수의 기능을 선언하도록 했다.


/** 
 * list 내에 checkbox 이벤트 처리:
 * 할일 처리상태 => label text-decoration
 * @param {Event} event 체크박스 클릭이벤트
 */
const checkToDo = (event) => {
  const label = event.target.nextSibling;
  if (event.target.checked) {
    label.setAttribute("class", "checked-label")
  } else {
    label.setAttribute("class", "unchecked-label")
  }
}

2-3. checkToDo(할 일 수행여부 체크) 기능을 위한 함수

더보기

WhatToDo(ul 태그) 내부에 있는 li 태그의 자식 요소 중, input type="checkbox" 인
체크박스의 체크 여부에 따라 DOM 조작을 통해 label 태그의 스타일링을 변환해주는 함수이다

click 이벤트 객체를 인자로 받아 해당 타겟의 바로 옆인 label 태그를 지정하여 상수에 할당하고,
체크 여부(event.target.checked가 true 혹은 false인 조건)에 따라 class 속성을 부여함으로
스타일링에 변화를 주도록 함수를 선언하였다.


/** 
 * list 내에 button을 통한 이벤트 처리:
 * parentNode를 이용해 list 삭제 + sessionStorage 최신화
 * @param {Event} event 삭제버튼 클릭이벤트
 */
const deleteToDo = (event) => {
  event.preventDefault();
  const li = event.target.parentNode;
  WhatToDo.removeChild(li);
  toDoList = toDoList.filter((toDo) => toDo.id !== Number(li.id));
  sessionStorage.setItem(ToDoList, JSON.stringify(toDoList));
}

2-4. deleteToDo(할 일 목록 삭제) 기능을 위한 함수

더보기

WhatToDo(ul 태그) 내부에 있는 li 태그의 자식 요소 중 button을 클릭했을 때
DOM 조작을 통해 li 전체를 삭제하고 sessionStorage를 최신화 처리해주는 함수이다

먼저 DOM 조작을 위해 click Event.target의 부모인 li 태그 전체를 상수로 할당하고,
해당 태그만 삭제될 수 있도록 WhatToDo 내에서 선언된 요소만 removeChild 함수를 통해 삭제처리 한다

이후 filter 함수를 통해 sessionStorage의 value인 toDoList의 id값과 li의 id값을 비교하여
해당 값을 제외한 나머지 값으로 데이터를 최신화 하여준다. 이후 sessionStorage에 해당 데이터를
다시 setItem을 통해 ToDoList의 value값으로 다시 할당하여 준다.
할당할때는 JSON 문자열이 필요하므로 해당 배열을 JSON.stringfy 함수를 통해 변환하여 할당해준다.


/** 
 * sessionStorage에 데이터가 있을 경우
 * 초기화면에 그려주는 역할
 */
const getToDo = () => {
  const loaded = sessionStorage.getItem(ToDoList);
  if (loaded !== null) {
    const parsed = JSON.parse(loaded);
    for (let toDo of parsed) {
      const {text} = toDo;
      paintToDo(text);
      setToDo(text);
    }
  }
}

2-5. getToDo(sessionStorage에 데이터가 있을때, 초기화면으로 그려주는 역할) 기능을 위한 함수

더보기

웹 페이지 접속시, 기존 접속이력이 유지된 상태일 경우(단순히 새로고침을 한 경우)에
기존에 입력되어 있던 데이터가 있다면 해당 데이터를 다시 불러와서 그려주는 함수이다

먼저 sessionStorage에서 ToDoList 데이터를 받아오기 위해 getItem 함수를 활용하여,
데이터를 상수 loaded에 할당하여주고 해당 데이터의 존재여부(data가 null인지 아닌지)에 따라

해당 내용을 그려줄 수 있도록 조건에 대한 분기를 설정하고, 만약 데이터가 있다면(null이 아니라면)
parsed 상수에 loaded 내의 데이터를 JavaScript 객체로 변환하여 해당 데이터 내의 text 값을
paintToDo 함수를 통해 화면에 그려주고, setToDo 함수를 통해 sessionStorage에 데이터를 반영하도록 한다


/** 
 * sessionStorage에 데이터를 추가하여
 * 새로고침시에도 데이터가 남아있게 해줌
 * @param {String} toDo input에 넣은 text(할 일)
 */
const setToDo = (toDo) => {

/** 
 * sessionStorage에 넣을 value 정제
 * @type {{text: String, id: Number}}
 */
  const obj = {
    text: toDo,
    id: toDoList.length + 1,
  }

  toDoList.push(obj);
  sessionStorage.setItem(ToDoList, JSON.stringify(toDoList));
}

2-6. setToDo(sessionStorage에 데이터를 반영) 기능을 위한 함수

더보기

sessionStorage의 활용을 위해, input value인 toDo를 인자로 받아 sessionStorage에
해당 데이터를 저장해주는 역할을 하는 함수이다

toDo 즉, input value를 인자로 받아 id와 text를 가진 객체인 obj에 데이터를 할당한다.
여기서 obj의 값은 id와 text로 이루어져 있는데, id는 toDoList의 length를 기준으로 하고,
text는 input value 값을 넣어주도록 한다

그리고 sessionStorage에 넘겨주기 위해 빈 배열에 해당 객체를 할당(추가)하고,
sessionStorage에 ToDoList라는 key 값을 통해 value로 JSON 문자열로 변환된 toDoList를 넘겨준다


/** 
 * 초기화면 및 form event 처리
 */
const initToDo = () => {
  getToDo();
  FormToDo.addEventListener("submit", createToDo);
}

initToDo();

2-7. initToDo(초기화면 구성 및 form event 추가) 기능을 위한 함수

더보기

웹 페이지 접속 시, sessionStorage의 데이터 유무에 따라 화면을 그려주고
form의 submit에 대한 Event를 createToDo 함수에 할당해주는 함수이다.

해당 함수는 script 호출시 바로 실행이 될 수 있도록
해당 스크립트 파일 내에서 실행을 바로 시켜준다.


3. 스타일 작성 (SCSS 문법을 사용한 SASS 적용)

@font-face {
  font-family: 'EarlyFontDiary';
  src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_220508@1.0/EarlyFontDiary.woff2') format('woff2');
  font-weight: normal;
  font-style: normal;
}

html { 
 font-family: 'EarlyFontDiary';
}

body {
  margin: 0;
}

header {
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;

  h1 {
    color: rgb(255, 104, 104);
    font-size: 40px;
    font-weight: bold;
  }
}

section {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;

  .input-to-do {
    margin-bottom: 10px;

    input {
      padding: 5px;
      font-size: 16px;
      font-family: 'EarlyFontDiary';
    }
    button {
      padding: 5px;
      font-size: 16px;
      font-family: 'EarlyFontDiary';
    }
  }

  article {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    margin-bottom: 15px;

    div{
      width: 100%;
      height: 100%;
      min-width: 120px;
      min-height: 60px;
      background: rgb(255, 122, 122);
      border-radius: 10px;
      padding: 20px;

      @media screen and (max-width:768px) {
        max-width: 320px;
      }
      
      .what-to-do {
        width: 100%;
        padding: 0;
        margin: 0;
        list-style-type: none;

        :last-child {
          margin-bottom: 0;
        }

        @media screen and (max-width:768px) {
          max-width: 320px;
        }
        
        li {
          display: flex;
          justify-content: space-between;
          align-items: center;
          background: rgb(255, 193, 193);
          border-radius: 10px;
          padding: 20px;
          margin-bottom: 20px;

          @media screen and (max-width:768px) {
            max-width: 280px;
          }

          input {
            width: 25px;
          }

          label {
            min-width: 180px;
            margin: 0 10px;
            text-align: center;

            @media screen and (max-width:768px) {
              max-width: 180px;
              max-height: 18px;
              overflow: scroll;
            }
          }

          .checked-label {
            min-width: 180px;
            margin: 0 10px;
            text-align: center;
            color: #7b7b7b;
            text-decoration: line-through;

            @media screen and (max-width:768px) {
              max-width: 180px;
              max-height: 18px;
              overflow: scroll;
            }
          }
  
          button {
            width: 45px;
            font-family: 'EarlyFontDiary';
          }
        }
      }
    }
  }
}

footer {
  width: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);

  div {
    background: rgba($color: #ffffff, $alpha: 0.5);
    font-size: 12px;
    padding: 5px;
    border-radius: 5px;
  }
}

해당 코드를 통해 아래와 같은 화면 출력을 확인할 수 있다.

할 일 목록 생성
할 일 완료 표시


이번 포스팅은 좀 역대급으로 지치는 포스팅이었던 것 같다.

아직 설명에 대해서는 부족한게 많아서 하나하나 설명하려다 보니
좀 더 에너지를 많이 쏟은 것 같다.

 

체력이 남는다면 다음 포스팅에서는, JSDoc을 조금 다뤄보도록 하겠다!

반응형

관련글 더보기

댓글 영역