TIL 01 | vanilla todo 리뷰

song hyun·2021년 7월 5일
0

JavaScript

목록 보기
1/19
post-thumbnail

Project Overview

todo-list page
todo-list gitHub

라이브러리를 사용하지 않고 순수하게 자바스크립트 언어로만 프로그래밍을 하고 싶어서 투두리스트를 선택하게 만들게 되었다.

작업 기간
2021.06.28 ~ 2021.07.05

기술 스택

  • HTML/CSS
  • JavaScript(ES6+)
  • Git

구현 사항

  • 사용자가 할 일을 등록할 때는 최소 한 글자 이상을 입력해야 등록할 수 있다.
  • 등록된 일은 할 일 목록에 보여지며, 각각의 일들은 수정 또는 삭제 할 수 있다.
  • 등록된 일은 체크박스 누르면 완료된 목록으로 이동하게 된다.
  • 완료된 목록의 일들은 삭제 또는 할 일 목록으로 이동만 할 수 있다.

결과화면

할 일 등록

삭제

할 일 수정

목록 이동

Project Review

javascript는 실시간으로 렌더링 하지 않는다.

javascript는 react처럼 실시간으로 화면 대한 변화를 렌더링 하지 않기 때문에 따로 렌더링 하는 함수를 구현해서 사용해야 된다.

// render
const renderTodo = () => {
  let reverse, item, join;

  reverse = [...todoList];
  item = reverse.filter(incomplete);
  join = item.map(incompleteTemplate).join('');
  INCOMPLETE.innerHTML = join;
  
  reverse = [...completedList];
  item = reverse.filter(completed);
  join = item.map(completedTemplate).join('');
  COMPLETED.innerHTML = join;
};

Node.insertBefore()

appendChild()와 비슷해보이지만, inrestBefore()는 전달 인자가 두 개이다. 첫 번째 전달인자 newNode는 삽입할 노드, 두 번째 전달 인자 referenceNode는 기준점 노드이다.

let insertedNode = parentNode.insertBefore(newNode, referenceNode);

느낀 점

  • 할 일 입력하고 난 후 엔터(enter)를 누르면 등록이 되게 해야되는데 그 부분을 처음부터 생각하지 못한게 아쉬웠다.

  • 글로 봤을 때는 잘 와닿지 않는 코드 부분들을 실제 내가 작성하면서 사용되니까 더 빠른 이해가 되었다. 예를 들면 func.bind(), node.insertBefore() 등등

  • 돌다리도 두드려보고 건너기, 코드가 완성 되었다고 방심하지 말고 리팩토링하여 검토해야겠다.

HTML Code

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

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>vanilla todo</title>
  <link rel="stylesheet" href="css/reset.css">
  <link rel="stylesheet" href="css/style.css">
  <script defer src="js/todo.js"></script>
</head>

<body>
  <main class="todoContent">
    <header>
      <h1 class="title">todo list</h1>
    </header>
    <!-- add item   -->
    <section>
      <h2>add item</h2>
      <form class="add-form">
        <div>
          <label for="addInput" class="a11y-hidden">할 일 추가</label>
          <input type="text" id="addInput" class="add-input" required />
          <button type="submit" class="add-btn">add</button>
        </div>
      </form>
    </section>
    <!-- // add item  -->
    <!-- todo list   -->
    <section class="todo">
      <h2>todo</h2>
      <ul class="incomplete">
      </ul>
    </section>
    <!-- // todo list   -->
    <!-- completed list    -->
    <section class="todo">
      <h2>completed</h2>
      <ul class="completed">
      </ul>
    </section>
    <!-- completed list   -->
  </main>
</body>

</html>

css(rest.css) Code

@charset "utf-8";

/* margin, padding */
body, 
main, 
div, 
span,
section, 
label,
input,
form, 
button,
ul,
h1,
h2,
h3,
p,
i{
  margin: 0;
  padding: 0;
}

/* label */
label{
  background: transparent;
}

/* input */
input{
  border: none;
  outline:none;
  background: transparent;
}

input[type="checkbox"]{
  cursor: pointer;
}

/* button */
button{
  border: none;
  outline: none;
  text-transform: capitalize;
  background: transparent;
  cursor: pointer;
}

/* list */
ul,
li{
  list-style:none;
}

/* title  */
h1,
h2,
h3{
font-weight: normal;
text-transform: uppercase;
}

i {
  font-style: normal;
}

body, 
input, 
button, 
div,
label,
span,
p, 
h1,
h2,
h3,
ul, 
li{
  font-family: 'Roboto Condensed', sans-serif;
  font-size: 1rem;
  line-height: 1.5;
  color: #fff;;
}

.a11y-hidden {
  overflow: hidden;
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  clip: rect(0 0 0 0);
  clip: rect(0, 0, 0, 0);
}

css(style.css) Code

@charset "utf-8";

body {
  overflow: scroll;
  background:#f5f3e9 ;
}

main{
  width: 28.13rem;
  margin: 6.25rem auto;
  padding: 1.25rem;
  box-shadow: -1.25rem -1.25rem 0px 0px rgb(100 100 100 / 10%);
  background: #98c0b7;
  color: #fff;
}

h1{
  font-size: 1.667rem;
  text-align: center;
}

h2{
  font-size: 1.375rem;
  margin-top: 0.625rem;
  border-bottom: 1px solid #fff;
}

/* add-form */
.add-form > div{
  display: flex;
  justify-content: space-between;
}

/* add-form input */
.add-input{
  width: calc(100% - 70px);
  height: 2.5rem;
  margin: 0.625rem 0;
  padding: 0 0.625rem;
  font-size: 1.667rem;
  line-height: 2.5rem;
  background: #fff;
  color: black;
}

/* add-form button */
.add-btn{
  font-size: 1.25rem;
}

/* todo - ul li */
.todo ul li{
  display: flex;
  justify-content: space-between;
  border-bottom: 1px solid #a2cec4;
}

/* todo - ul li checkbox  */
.todo li input[type="checkbox"]{
  width: 1rem;
  height: 1rem;
  margin: 0.8125rem 0.625rem 0 0;
  line-height: 1rem;
}

/* todo -ul.incomple li label */
.todo .incomplete li label{
  width: calc(100% - 165px);
  margin : 0.5625rem 0;
  height:1.563rem;
  line-height: 1.563rem;
  font-size: 1.5rem;
}

/*  todo -ul.incomple li input(edit) */
.todo .incomplete li input[type="text"]{
  width: calc(100% - 165px);
  height: 1.563rem;
  margin: 0.5rem 0;
  border: 1px solid #91d4c5;
  line-height: 1.563rem;
  font-size: 1.5rem;
}

/* todo -ul.completed li label */
.todo .completed li label{
  width: calc(100% - 85px);
  height: 1.563rem;
  margin: 0.5625rem  0;
  line-height: 1.563rem;
  font-size: 1.5rem;
}

/* todo - li butgrop buttons */
.todo-btngroup button{
  padding: 0 0.625rem 0 0;
  margin: 0.5rem 0.625rem 0 0;
  font-size: 1.25rem;
}

/* todo - li btngroup saveBtn(edit) */
.todo-btngroup .save-btn{
  padding: 0;
}

.todo-btngroup button:last-child{
  padding: 0;
}

JavaScript Code

(() => {
  let todoList = [];
  let completedList = [];

  const LOADED_TODOLIST = localStorage.getItem('TODO');
  const COMPLETED_LIST = localStorage.getItem('COMPLETED');

  const TODO_CONTENT = document.querySelector('.todoContent');
  const ADD_INPUT = document.querySelector('.add-input');
  const INCOMPLETE = document.querySelector('.incomplete');
  const COMPLETED = document.querySelector('.completed');

  // initialize and execute
  const init = () => {
    addEvent();
    loadTodoList();
    loadCompletedList();
    renderTodo();
  };

  // loading todoList
  const loadTodoList = () => {
    if (LOADED_TODOLIST !== null) {
      todoList = JSON.parse(LOADED_TODOLIST);
      return todoList;
    }
  };

  // loading compltedList
  const loadCompletedList = () => {
    if (COMPLETED_LIST !== null) {
      completedList = JSON.parse(COMPLETED_LIST);
      return completedList;
    }
  };

  // event listener
  const addEvent = () => {
    let li, id, index, todo, list, checkbox, item;
    let label, input, save_btn, edit_btn;

    TODO_CONTENT.addEventListener('click', (e) => {
      // add button
      if (e.target.className === 'add-btn') {
        addFormSubmit();

        // delete button
      } else if (e.target.className === 'delete-btn') {
        li = e.target.parentNode.parentNode;
        id = Number(li.getAttribute('data-id'));
        checkbox = li.querySelector('input[type="checkbox"]').value;
        list = checkbox === 'true' ? [...completedList] : [...todoList];
        index = list.findIndex(matchingID.bind(todo, id));

        list.splice(index, 1);
        list = checkbox === 'true' ? (completedList = list) : (todoList = list);

        renderTodo();
        saveTodoList();
        saveCompletedList();
        // edit button
      } else if (e.target.className === 'edit-btn') {
        list = [...todoList];
        li = e.target.parentNode.parentNode;
        id = Number(li.getAttribute('data-id'));
        item = todoList.find(matchingID.bind(todo, id));

        label = li.querySelector('label');
        label.classList.add('a11y-hidden');

        save_btn = li.querySelector('.save-btn');
        save_btn.classList.remove('a11y-hidden');

        edit_btn = li.querySelector('.edit-btn');
        edit_btn.classList.add('a11y-hidden');

        input = document.createElement('input');
        input.setAttribute('type', 'text');
        input.setAttribute('id', id);
        input.setAttribute('value', item.contents);

        li.insertBefore(input, label.nextSibling);

        // save button
      } else if (e.target.className === 'save-btn') {
        list = [...todoList];
        li = e.target.parentNode.parentNode;
        id = Number(li.getAttribute('data-id'));
        item = list.find(matchingID.bind(todo, id));
        index = list.findIndex(matchingID.bind(todo, id));

        input = li.querySelector('input[type="text"]');
        item.contents = input.value;

        save_btn = li.querySelector('.save-btn');
        save_btn.classList.add('a11y-hidden');

        edit_btn = li.querySelector('.edit-btn');
        edit_btn.classList.remove('a11y-hidden');
        list.splice(index, 0);
        todoList = list;

        saveTodoList();
        renderTodo();

        // incomplete if checkbox value is false
      } else if (e.target.type === 'checkbox' && e.target.value === 'false') {
        list = [...todoList];
        id = Number(e.target.id);
        index = list.findIndex(matchingID.bind(todo, id));
        item = list.find(matchingID.bind(todo, id));

        item.isCompleted = !item.isCompleted;
        completedList.push(item);
        list.splice(index, 1);
        todoList = list;

        renderTodo();
        saveTodoList();
        saveCompletedList();

        // complete if checkbox value is true
      } else if (e.target.type === 'checkbox' && e.target.value === 'true') {
        list = [...completedList];
        id = Number(e.target.id);
        index = list.findIndex(matchingID.bind(todo, id));
        item = list.find(matchingID.bind(todo, id));

        item.isCompleted = !item.isCompleted;
        todoList.push(item);
        list.splice(index, 1);
        completedList = list;

        renderTodo();
        saveTodoList();
        saveCompletedList();
      }
    });
  };

  // add item form validation
  const addFormSubmit = () => {
    let VALUE = ADD_INPUT.value;

    if (VALUE === '') {
      return false;
    }

    ADD_INPUT.value = '';
    addTodoDate(VALUE);
  };

  // add an item to the list
  const addTodoDate = (value) => {
    todoList.push({
      id: Math.floor(Math.random() * 999),
      contents: value,
      isCompleted: false,
    });

    renderTodo();
    saveTodoList();
  };

  // incompleteTemplete
  const incompleteTemplate = (item) => {
    const INCOMPLETE_ITEM = `
    <li data-id=${item.id}>
    <input id=${item.id} type="checkbox" value=${item.isCompleted} />
    <label for=${item.id}>${item.contents}</label>
    <div class="todo-btngroup">
      <button type="button" class="edit-btn">edit</button>
      <button type="button" class="a11y-hidden save-btn">save</button>
      <button type="button" class="delete-btn">delete</button>
    </div>
  </li>
    `;

    return INCOMPLETE_ITEM;
  };

  const incomplete = (item) => {
    return !item.isCompleted;
  };

  // completed template
  const completedTemplate = (item) => {
    const COMPLETED_ITEM = `
    <li data-id=${item.id}>
    <input id=${item.id} type="checkbox" value=${item.isCompleted} />
    <label for=${item.id}>${item.contents}</label>
    <div class="todo-btngroup">
      <button type="button" class="delete-btn">delete</button>
    </div>
  </li>
    `;
    return COMPLETED_ITEM;
  };

  const completed = (item) => {
    return item.isCompleted;
  };

  // matching item
  const matchingID = (id, item) => {
    return item.id === id;
  };

  // render
  const renderTodo = () => {
    let reverse, item, join;

    // todoList
    reverse = [...todoList];
    item = reverse.filter(incomplete);
    join = item.map(incompleteTemplate).join('');
    INCOMPLETE.innerHTML = join;

    reverse = [...completedList];
    item = reverse.filter(completed);
    join = item.map(completedTemplate).join('');
    COMPLETED.innerHTML = join;
  };

  // save to todoList LocalStorage
  const saveTodoList = () => {
    localStorage.setItem('TODO', JSON.stringify(todoList));
  };

  // save to completedList LocalStorage
  const saveCompletedList = () => {
    localStorage.setItem('COMPLETED', JSON.stringify(completedList));
  };

  init();
})();
profile
Front-end Developer 🌱

0개의 댓글