ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Calendar 코드리뷰 - JavaScript 적용하기
    프로젝트 리뷰/기초 JS로 Calendar 구현하기 2020. 5. 3. 16:13

    ※ 목차

    1. Calendar 구현 목표

    2. Calendar 코드리뷰 - HTML마크업

    3. Calendar 코드리뷰 - CSS 적용하기

    4. Calendar 코드리뷰 - JavaScript 적용하기

    5. Calendar - 프로젝트를 마치며

     

    최종 완성본

     

     

     

    구현 목표 순서

     

    1.  첫 로딩이 되면 현재 출력하고자 하는 달이 윤년인지 윤년체크를 한다.

     

    2.  윤년 체크가 완료되면 해당하는 달의 일 수만큼 html 요소들을 createElement 메서드로 만들고 필요한 속성들도 setAttribute 메서드로 정의 해준다.

     

    3.  왼쪽 파트에 현재 날짜를 보여주기 위해 함수를 작성하는데 이 함수는 재활용 할 여지가 있으므로 따로 함수로 만들어준다.

     

    4.  첫 로딩이 되면서 해당 날짜에 등록된 할일 목록이 있으면 출력해줄 수 있는 함수를 만들어준다. 이 함수 역시 재활용 할 여지가 있으므로 따로 만들어준다.

     

    5.  이전 달, 다음 달을 클릭하면 이전 달과 다음 달을 보여주기 위해 이벤트리스너를 걸어주고 해당하는 달에 맞는 캘린더를 화면에 뿌려준다.

     

    6.  5번에서 뿌려줄 때, 기존에 있던 달력과 중복해서 출력 될 것이므로 기존 달력은 지워준 후에 출력 해주어야 한다. 따라서, 기존 달을 지워주는 함수를 재활용성을 고려하여 따로 만들어 준다.

     

    7.  DATA를 담을 객체가 있는 DATA.js 파일을 하나 더 만들어 놓는다.

     

    8. input에 할일을 입력하고 enter 또는 INPUT 버튼을 클릭하면 DATA에 차례대로 value 값들이 들어가고, createElement 메서드로 li 태그와 할일 목록을 지울 수 있는 delete 버튼을 생성한다.

     

    9. 작성한 할일 목록의 글이 길어지면 ...으로 생략되어 삽입되고 해당 목록을 더블클릭하여 확인할 수 있다.

     

    10.  DATA에 들어가 있는 목록들을 LocalStorage 에 저장한다.

     

     

     

    구현하기

     

    첫 로딩시 달력 출력하기 

    function buildCalendar() {
      let firstDate = new Date(today.getFullYear(), today.getMonth(), 1);
      const monthList = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
      const leapYear = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
      const notLeapYear = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
      const headerYear = document.querySelector('.current-year-month');
      // 윤년 체크하기
      if (firstDate.getFullYear() % 4 === 0) {
        pageYear = leapYear;
      } else {
        pageYear = notLeapYear;
      }
      headerYear.innerHTML = `${monthList[firstDate.getMonth()]}    ${today.getFullYear()}`;
      makeElement(firstDate);
      showMain();
      currentDateget();
      resetInsert();
    }

    전역에 today라는 변수명이 있고 이 변수에는 new Date(); 한 값이 들어가 있습니다.

    firstDate 라는 변수안에 해당하는 달의 첫 번째 요일을 넣어주고 makeElement()함수에 이 변수를 전달인자로 넣어 호출합니다.

     

    엘리먼트 만들기

    function makeElement(firstDate) {
      let weekly = 100;
      let Currentdate = 1;
      for (let i = 0; i < 6; i++) {
        let weeklyEl = document.createElement('div');
        weeklyEl.setAttribute('class', weekly);
        weeklyEl.setAttribute('id', "weekly");
        for (let j = 0; j < 7; j++) {
          // i === 0이여야 하는 이유는 첫 날짜를 찍고 그 다음 날짜가 0번째 칸부터 다시 그려져야 하기 때문
          // firstDate.getMonth() => 현재 달의 일수가 몇일인지 반환해주고, 이 조건은 반환 된 값에 따라 출력해 준 후, 달력 출력 종료조건이다.
          if (i === 0 && j < firstDate.getDay() || Currentdate > pageYear[firstDate.getMonth()]) {
            // 만약 해당 칸에 날짜가 없으면 div엘리먼트만 생성한다.
            let dateEl = document.createElement('div');
            weeklyEl.appendChild(dateEl);
          } else {
            // 해당 칸에 날짜가 있으면 div엘리먼트 생성 후 해당 날짜 넣어주기
            let dateEl = document.createElement('div');
            dateEl.textContent = Currentdate;
            dateEl.setAttribute('class', Currentdate);
            dateEl.setAttribute('id', `${today.format2()}-${Currentdate}`);
            weeklyEl.appendChild(dateEl);
            Currentdate++;
          }
        }
        weekly++;
        calendarBody.appendChild(weeklyEl);
      }
      // 현재 내가 선택한 날짜가 있으면 이전 달, 다음 달로 넘어가도 화면에 보여주기 위해 써줌
      let clickedDate = document.getElementsByClassName(today.getDate());
      clickedDate[0].classList.add('active');
    }

    먼저 for문은 두번 반복할 것입니다. 왜냐하면 한 주를 만들어주기 위한 첫 번째 for문이 필요하고 한달에 최대 6주 이상인 경우는 없으므로 i가 6 보다 작을 때 까지 반복하면서 한 주를 출력할 div 태그를 만듭니다.

    두 번째 for 문을 반복할 때에는 일주일은 7일 이므로 i 가 7보다 작을 때까지 반복하고, 조건문으로 출력 종료조건을 넣어주어야 합니다.

    i === 0 인 이유는 이 조건을 넣어주지 않으면 각 날짜별로 출력을 하면서 그 다음 주로 넘어 갈 때, 맨 앞에서부터 출력하지 않기 때문입니다Currentdate 는 해당 날짜를 넣어줄 변수명으로 하나의 태그가 생성되면 Currentdate++; 로 하나씩 증가 시켜 줍니다.

    다 만들어진 HTML 요소들을 부모 태그인 calendarBody에 appendChild() 메서드로 생성완료 시켜 줍니다.

     

    이전 달, 다음 달 클릭시 해당하는 달 보여주기

    prevEl.addEventListener('click', function () {
      today = new Date(today.getFullYear(), today.getMonth() - 1, today.getDate());
      removeCalendar();
      buildCalendar();
      resetInsert();
      redrawLi()
    });
    
    nextEl.addEventListener('click', function () {
      today = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
      removeCalendar();
      buildCalendar();
      resetInsert();
      redrawLi()
    });

     전역에 선언된 변수 today에 현재 달에서 -1 또는 +1 로 재 할당을 해준 것말고는 모두 함수 호출만 존재합니다.

    재활용성을 생각하여 함수별로 나누어 주었기 때문에 가독성도 좋아지고 간결한 코드작성도 가능해졌습니다.

     

    input 에 할일 목록을 작성하여 제출하면 DATA 객체에 저장하고 해당 리스트 태그 만들어주기

    function insertTodo(text) {
      let todoObj = {
        todo: text,
      }
      if (!DATA[currentDate]) {
        DATA[currentDate] = [];
        DATA[currentDate].push(todoObj);
      } else {
        DATA[currentDate].push(todoObj);
      }
      const liEl = document.createElement('li');
      const spanEl = document.createElement('span');
      const delBtn = document.createElement('button');
      delBtn.innerText = "DEL";
      delBtn.setAttribute('class', 'del-data');
      spanEl.innerHTML = text;
      liEl.appendChild(spanEl);
      liEl.appendChild(delBtn);
      inputList.appendChild(liEl);
      liEl.setAttribute('id', DATA[currentDate].length);
      delBtn.addEventListener('click', delWork);
      liEl.addEventListener('dblclick', showTodo);
      // todoObj에 id값을 114번 줄에서 넣어주면 DATA[currentDate].length 값을 찾아올 수 없기 때문에 push해준 후 에 추가하여 local에 저장한다.
      todoObj.id = DATA[currentDate].length;
      save();
      inputBox.value = '';
    }

    todoObj 라는 객체가 만들어 지고 DATA[해당 날짜] 가 없으면 빈 배열을 생성 후 push , DATA[해당 날짜] 가 있다면 push 만 해줄 수 있게 조건문처리를 해주었고, createElement()메서드로 필요한 태그들을 만들고 속성들을 부여해주었습니다.

    이 작업이 끝나고 나면 save()라는 함수를 통해 LocalStorage 에 데이터를 저장하는 함수를 실행 시킵니다.

     

     

    다음 달,이전 달 다른 날, 첫 로드 될 때 마다 할일 목록이 있으면 다 지우고 다시 그려주는 함수

     

    function resetInsert() {
      let storeObj = localStorage.getItem(currentDate);
      if (storeObj !== null) {
        let liEl = document.querySelectorAll('LI');
        for (let i = 0; i < liEl.length; i++) {
          inputList.removeChild(liEl[i]);
        }
        // parse 해주기 전에는 localStorage는 string만 가져오니까 parse해준다.
        const parsed = JSON.parse(localStorage.getItem(currentDate));
        // forEach로 작성되있는 모든 todolist의 항목들을 돌면서 로컬에 저장되어 있는 목록을 화면에 만들어준다.
        parsed.forEach(function (todo) {
          if (todo) {
            let lili = document.createElement('li');
            let spanspan = document.createElement('span');
            let deldel = document.createElement('button');
            deldel.setAttribute('class', 'del-data');
            deldel.innerText = "DEL";
            lili.setAttribute('id', todo.id);
            spanspan.innerHTML = todo.todo;
            lili.appendChild(spanspan);
            lili.appendChild(deldel);
            inputList.appendChild(lili);
            deldel.addEventListener('click', delWork);
            lili.addEventListener('dblclick', showTodo);
          }
        });
      }
    }

    먼저 기존에 있던 할일 목록들을 removeChild() 메서드로 다 지워주어야 합니다.

    그래야 해당하는 날짜에 등록된 할일 목록들만을 보여 줄 수 있기 때문입니다.

     

    localStorage는 읽기 전용으로 오직 string만 가져올 수 있습니다. 데이터를 저장할 때에도 문자열로 저장합니다.

    localStorage.getItem() 으로 로컬스토리지에 있는 데이터들을 가져왔고 받아온 데이터들을 forEach() 메서드로 순회하면서 todo 가 존재 한다면 다시 li요소들을 그려주는 작업을 하게 됩니다.

     

    할일 목록의 글 더블클릭해서 보기

     

    할일 목록의 글이 길어지게 되면 ...으로 생략되어 나옵니다. 그렇게 나온다면 보기 불편하고 뭐라고 작성되어 있는지 알 수 없기 때문에 확인하고자 하는 목록을 더블클릭하면 화면에 팝업창 처럼 해당 목록의 글을 보여줄 수 있게 합니다.

    이 팝업창이 뜨게 되면 다른 부분들은 클릭이 되지 않도록 처리해줍니다.

     

     

    할일 목록 삭제하기

    할일 목록이 만들어지면 옆에 자동으로 x 버튼도 생성됩니다.

    이 버튼을 누르면 내가 삭제하고자 하는 목록만 삭제되어야 하며, 삭제가 되는 동시에 DATA에서도 삭제되어야 합니다.

    DATA에서 삭제가 된다면 LocalStorage에서도 자동으로 삭제가 될 것입니다.

    function delWork(e) {
      e.preventDefault();
      let delParentLi = e.target.parentNode;
      inputList.removeChild(delParentLi);
      // DATA[currentDate]를 filter함수를 이용해 todo로 돌면서 todo의 아이디값과 현재 내가 누른 아이디값이 같지 않은 것을 배열에 담아 리턴해주어서 
      // 내가 지우고자 하는 요소를 뺀 나머지 요소를 배열에 담아 리턴해준다. 
      // 그 배열을 다시 DATA[currentDate]에 할당하여 save();를 통해 localStorage에 넣어준다.
      const cleanToDos = DATA[currentDate].filter(function (todo) {
        return todo.id !== parseInt(delParentLi.id);
      });
      DATA[currentDate] = cleanToDos;
      save();
    }

     

    LocalStorage에 DATA 객체 저장하기

    function save() {
      localStorage.setItem(currentDate, JSON.stringify(DATA[currentDate]));
    }

    setItem() 메서드로 간단하게 저장할 수 있습니다.

    단, 로컬스토리지는 읽기전용이고 문자열만을 다루기 때문에 JSON.stringify 를 통해 넣어주어야 합니다.

     

     

    이 외의 나머지 코드들은 제 깃허브에 모든 소스들을 오픈하여 올려두었으니 궁금하신 분들은 아래의 링크로 가셔서 확인하시면 되겠습니다.

     

    전체소스 보러 깃허브 바로가기 - https://github.com/HyojinLee96

    다음주제 바로가기 - Calendar 프로젝트를 마치며

Designed by Tistory.