💡뚝딱뚝딱 만들어보자 ~! :)/[Vanila JS] Calender Todo

[Vanila JS] Calender Todo - Calender Logic

코딩가링가링 2024. 5. 25. 20:05

바닐라 JS를 이용해서 간단한 ToDo를 입력 받을 수 있는 캘린더를 만들어보자 다짐했었다.

지난 포스팅에서는 내가 직접 디자인 한 UI를 구현했고, 오늘은 그 안의 내용을 채워줄 JavaScript 로직을 뜯어볼 것이다.

 

크게 캘린더 부분의 날짜를 불러오고 내가 원하는 날짜로 이동하는 로직과 지정한 날짜에 ToDo List를 입력하고 저장하는 로직으로 나뉠 것이다.

 

UI를 어떻게 구현했는지 궁금하다면

이전 포스팅 보러가기

 

[Vanila JS] Calender Todo - UI

캘린더와 함께 간단한 할 일을 메모할 수 있는 사이트를 만들어보자 생각한 지 언....(기억 안남)그래서 이번에 프로젝트도 끝났겠다, 프론트엔드 멘토에서 얻은 의욕을 여기에 써보자는 생각이

codingaring.tistory.com

 

 

이번 달 날짜 불러오기

이번 달의 날짜들을 불러오기 위해서는 어떤 정보들이 필요할까?

  • 이번 달이 몇 년도 몇 월인지
  • 이번 달이 몇 일까지 있는지, 마지막 일이 무슨 요일인지
  • 앞 뒤로 달력에 포함될 전 월의 마지막 일자들과 후 월의 앞부분 일자

당장의 캘린더 한 장을 채우기 위한 정보는 이 정도만 있으면 될 것 같다.

그럼 바로 코드로 작성해보자!

// 오늘 날짜로 Date 객체 생성하기
const date = new Date();

// 오늘 날짜의 해와 월, 일 구하기
const currentYear = date.getFullYear();
const currentMonth = date.getMonth();
const day = date.getDate();

// year-month 태그 안에 구한 년과 월을 띄운다.
// 이번 달을 구하기 위해서는 +1 을 해주어야 한다.
document.querySelector(".year-month").textContent = `${currentYear}년 ${
  currentMonth + 1
}월`;

// 이전 달의 마지막 Date와 현재 월의 마지막 Date를 구한다. (0을 주면 마지막 일로 생성)
// Tue Apr 30 2024 00:00:00 GMT+0900 (한국 표준시) 형식으로 반환 됨
const prevLastDate = new Date(currentYear, currentMonth, 0);
const currentLastDate = new Date(currentYear, currentMonth + 1, 0);

// 각 각의 Date에서 일자만 뽑아낸다.
// 30 형식으로
const prevLast = prevLastDate.getDate();
const prevLastDay = prevLastDate.getDay();

// 현재 달의 마지막 일자와 요일을 구한다.
const currentLast = currentLastDate.getDate();
const currentLastDay = currentLastDate.getDay();


const prevDates = [];
// 이번 달의 Date 객체의 일자만 뽑아내어 배열에 넣고, slice(1)하여 한 날짜씩 끊는다.
/** 사실상 이 로직과 동일한 과정
thisDates -> for (let i = 1; i <= currentLast; i++) {
  prevDates.push(i);
} */
const thisDates = [...Array(currentLast + 1).keys()].slice(1);
const nextDates = [];

// 전 달의 마지막 일자가 토요일이 아니면, (토요일이면 들어갈 자리 없음, 바로 이번 달 시작하면 됨)
// prevDates배열에 마지막 요일 - i 만큼 뺀 값을 앞에 추가한다.
// 전 달의 마지막 요일이 수요일이면 4이면 4개의 일자가 붙어야 함
if (prevLastDay !== 6) {
  for (let i = 0; i < prevLastDay + 1; i++) {
    prevDates.unshift(prevLast - i);
  }
}

// 이번달의 요일을 7에서부터 빼서 다음 달의 초기 일자를 구한다.
for (let i = 1; i < 7 - currentLastDay; i++) {
  nextDates.push(i);
}

// 한 달력에 들어갈 요일들을 concat으로 합친다.
const dates = prevDates.concat(thisDates, nextDates);

// 그냥 반복문으로 쭉 넣어줘도 되지만, 
// 이전 달 및 이후 달의 일자는 회색으로 표시되길 원해서 조건을 걸어주었다.
dates.forEach((date, i) => {
  if (i < prevDates.length) {
    dates[i] = `<div class="date prev">${date}</div>`;
  } else if (i > prevDates.length + thisDates.length) {
    dates[i] = `<div class="date prev">${date}</div>`;
  } else if (prevDates.length < i < dates.length - nextDates.length) {
    dates[i] = `<div class="date">${date}</div>`;
  }
});

// dates태그 안에 dates의 배열들을 하나씩 끊어서 넣어준다.
document.querySelector(".dates").innerHTML = dates.join("");

이렇게 코드를 작성하면 아래와 같이 잘 들어가는 것을 볼 수 있다.

 

묘하게 잘 안보이지만, 잘 들어갔다.

하지만 여기서 만족할 수는 없다.

 

나는 상단의 화살표를 누르면 다음 달로, 이전 달로 마구마구 이동하는 자유로운 캘린더를 만들고 싶다.

코드를 짜기 전에 생각해보자. 어떻게 해줄 수 있을까?

 

이전 달, 이후 달 캘린더 확인하기

가장 먼저 든 생각은  화살표를 누를 때마다 현재 년도, 월의 값을 바꿔주면 될 것 같다.

이번에도 코드로 보자

...// /** 이전 로직 */

// change selectMonth
document.addEventListener("DOMContentLoaded", function () {
  const prevButton = document.querySelector(".nav-button.go-prev");
  const nextButton = document.querySelector(".nav-button.go-next");

  function handleSelectMonth(event) {
    /** 버튼이 이전 달로 가는 버튼인지 아닌지에 따라 다른 로직이 동작 */
    if (event.target.classList.contains("go-prev")) {
    /** 1월에서 이전 달로 가기 버튼을 누르면 년도가 이전 년도로 업데이트 */
    /** 12월에서 이후 달로 가기 버튼을 누르면 년도가 이후 년도로 업데이트 */
      if (selectMonth === 0) {
        selectYear--;
        selectMonth = 11;
      } else {
        selectMonth--;
      }
    } else if (event.target.classList.contains("go-next")) {
      if (selectMonth === 11) {
        selectYear++;
        selectMonth = 0;
      } else {
        selectMonth++;
      }
    }

	/** 캘린더의 날짜를 업데이트하는 로직 **/
    const prevLastDate = new Date(selectYear, selectMonth, 0);
    const currentLastDate = new Date(selectYear, selectMonth + 1, 0);

    const prevLast = prevLastDate.getDate();
    const prevLastDay = prevLastDate.getDay();

    const currentLast = currentLastDate.getDate();
    const currentLastDay = currentLastDate.getDay();

    const prevDates = [];
    const thisDates = [...Array(currentLast + 1).keys()].slice(1);
    const nextDates = [];

    if (prevLastDay !== 6) {
      for (let i = 0; i < prevLastDay + 1; i++) {
        prevDates.unshift(prevLast - i);
      }
    }

    for (let i = 1; i < 7 - currentLastDay; i++) {
      nextDates.push(i);
    }

    dates = prevDates.concat(thisDates, nextDates);

    dates.forEach((date, i) => {
      if (i < prevDates.length) {
        dates[i] = `<div class="date prev">${date}</div>`;
      } else if (i > prevDates.length + thisDates.length - 1) {
        dates[i] = `<div class="date prev">${date}</div>`;
      } else if (prevDates.length < i < dates.length - nextDates.length) {
        dates[i] = `<div class="date">${date}</div>`;
      }
    });

    document.querySelector(".dates").innerHTML = dates.join("");
    document.querySelector(".today").textContent = `${selectYear}년 ${
      selectMonth + 1
    }월`;
  }

  /** 버튼에 이벤트 등록 */
  if (prevButton) {
    prevButton.addEventListener("click", handleSelectMonth);
  }

  if (nextButton) {
    nextButton.addEventListener("click", handleSelectMonth);
  }
});

selectMonth와 selectYear을 바꿔줌과 동시에, 바꿔준 값에 맞는 날짜들로 다시 업데이트 해줘야하기 때문에 handler함수 안에서 또 다시 캘린더의 날짜를 채워주는 로직들이 반복된다.

이후, 추상화 과정을 통해서 반복되는 로직들을 줄이는 시간을 가질 예정이다.

 


 

오늘의 목표였던 캘린더 월별 전환까지 구현해보았다.

한 달의 그리드가 다섯 줄인 달과 여섯 줄인 달이 있어, 레이아웃 전체의 높이가 들쑥날쑥해지는 문제가 있었다.

그 부분을 개선하기 위해 CSS를 살짝 수정했다.

-> 이전 포스팅의 CSS 부분에 반영할 예정 - !

 

오늘의 결과물을 확인해보자~!

다음 구현 목표

  • 특정 날짜 선택하기
  • 선택된 날짜에 Todo 등록하기 (로컬 스토리지 이용 예정)

오늘은 여기까지 ~!

그럼 이만 !