본문 바로가기

Javascript/응용프로젝트

자바스크립트로 Date-Picker 구현하기

 

강의를 보고 난 뒤에 혼자 정리하는 느낌이라..
조금 조잡하고 설명이 정확하지 않을 수 있습니다
HTML,CSS는 강의자료로 받았고, 기능만 구현했습니다.

 


완성본


기능 설명

1. 달력을 toggle 로 보여주기

2. 현재 날짜 (년도, 월) 상단에 보여주기

3. 달에 맞는 일 계산해서 달력에 보여주기 + 첫 일이 무슨 요일인지 알아내서 그 요일부터 일이 생성될 수 있도록 함

4.  토요일과 일요일의 색상 컬러 바꿔주기

5. 현재 날짜 배경 컬러 바꿔주기 + 선택된 날짜의 배경 컬러 바꿔주기

6. 이전 버튼과 다음 버튼 클릭 시, 그 달의 데이터 보여주기

7. 선택 된 날짜 달력이 꺼졌을  때 보여주기 + 처음 접속했을 때, 현재 날짜 보여주기

8. 선택 된 날짜에서 달력 이동하다가, 달력 껐다 켰을 시에 선택 된 달로 보여주기


HTML 과 CSS

강의 자료이기때문에, 참고용으로 보시라고 캡쳐본으로 올립니다
* css 는 scss 를 사용했습니다.

index.html
style.scss


javascript 초기 작업

스크립트는 ES6 클래스 문법을 사용했습니다. ( 저도 아직 ES6 문법에 익숙치 않아서 기능 구현에 중점을 맞춰서 설명합니다 )

 

슬라이더 기능에서 사용 할 변수를 지정

  monthData = [
    'January',
    'February',
    'March',
    'April',
    'May',
    'June',
    'July',
    'August',
    'September',
    'October',
    'November',
    'December',
  ]; // month를 영문으로 보여주기 위해 만든 배열 데이터

  #calendarDate = {
    data: '',
    date: 0,
    month: 0,
    year: 0,
  }; // 캘린더의 데이터를 전역으로 사용하기 위해서 만든 객체 데이터

  selectedDate = {
    data: '',
    date: 0,
    month: 0,
    year: 0,
  }; // 선택된 캘린터의 데이터를 전역으로 사용하기 위해서 만든 객체 데이터

  datePickerEl;
  dateInputEl;
  calendarEl;
  calendarMonthEl;
  monthContentEl;
  nextBtnEl;
  prevBtnEl;
  calendarDatesEl;
  // html 에서 가져온 element 변수

 

사용 할 Element 가져오기

  datePickerEl; // data-picker 를 전체 요소를 묶고 있는 아이
  
  dateInputEl; // data-picker에서 달력을 toggle하고, 달력의 데이터를 보여주는 input
  
  calendarEl; // 달력을 묶고 있는 부모요소
  
  calendarMonthEl; // 달력에서 상단부분을 묶고 있는 부모요소
  
  monthContentEl; // 달력 상단에서 현재 년도와 월을 작성할 부분
  
  nextBtnEl; // 달력 다음 버튼
  
  prevBtnEl; // 달력 이전 버튼
  
  calendarDatesEl; // 달마다 일을 추가해줄 공간
  
  assignElement() {
    this.datePickerEl = document.getElementById('date-picker');
    this.dateInputEl = this.datePickerEl.querySelector('#date-input');
    this.calendarEl = this.datePickerEl.querySelector('#calendar');
    this.calendarMonthEl = this.calendarEl.querySelector('#month');
    this.monthContentEl = this.calendarEl.querySelector('#content');
    this.nextBtnEl = this.calendarEl.querySelector('#next');
    this.prevBtnEl = this.calendarEl.querySelector('#prev');
    this.calendarDatesEl = this.calendarEl.querySelector('#dates');
  }

 

초기화 작업

this에 바인딩되어 있는 인스턴스에 프로퍼티나 메서드를 추가하고 생성자 함수가 인수로 전달받은 초기값을 인스턴스 프로퍼티에 할당하여 초기화하거나 고정값을 할당하기위해 작업한 것 같아요
  • 현재 날짜를 만들어놓은 #calendarDate 변수에 데이터를 저장해주기 위한 함수
  initCalendarDate() {
    const data = new Date(); // 현재 날짜에 대한 데이터
    const date = data.getDate(); // 현재 날짜에서 일 데이터를 가져옴
    const month = data.getMonth(); // 현재 날짜에서 월 데이터를 가져옴
    const year = data.getFullYear(); // 현재 날짜에서 년도 데이터를 가져옴
    this.#calendarDate = {
      data,
      date,
      month,
      year,
    }; // 만들어놓은 변수 데이터에 가져온 데이터들을 넣어줌.
  }
  • 선택된 날짜가 없을 시에는, selectedDate 에 현재 날짜의 데이터를 넣어주는 함수
  initSelectedDate() {
    this.selectedDate = { ...this.#calendarDate };
  }
  • addEventListener 함수를 사용하는 코드 묶어놓기
addEvent(){ 추후에 들어갈 addEventListener 함수 )
  • constructor 에 클래스 필드를 선언하고 초기화 해주기
  constructor() {
    this.initCalendarDate();
    this.initSelectedDate();
    this.assignElement();
    this.addEvent();
  }
추후에 더 추가 될 예정

달력 Toggle 기능

 

dateInputEl (달력 input) 에게 "click" 이벤트를 준다.

  addEvent() {
    this.dateInputEl.addEventListener('click', this.toggleCalendar.bind(this)); // toggle 기능 구현
  }

 

toggleCalendar() 구현하기

  toggleCalendar() {
    this.calendarEl.classList.toggle('active');
 }
미리 만들어놨던 "active" 라는 클래스를 classList.toggle 메소드를 사용해, 추가해주고 다시 누르면 제거될 수 있도록 구현

날짜 업데이트

 

updateMonth()

  updateMonth() {
    this.monthContentEl.textContent = `${this.#calendarDate.year} ${
      this.monthData[this.#calendarDate.month]
    }`;
  }
달력 상단부분에, 현재 년도와 현재 달을 가져와 textContent 를 사용해 동적으로 작성하게 해준다.
this.monthData[this.#calendarDate.month] 를 사용한 이유
- getMonth() 메소드를 사용해, 월 데이터를 가져올 때는 인덱스 번호로 알려주기 때문에 만들어놓은 영문 월 데이터 배열에서 인덱스번호에 맞는 월 데이터를 가져오기 위해서다.

getMonth() 콘솔에 찍었을 때 모습 (1월 기준)

 

updateDates()

  updateDates() {
    this.calendarDatesEl.innerHTML = ''; // 만들어놓은 일 데이터를 초기화
    const numberOfDates = new Date(
      this.#calendarDate.year,
      this.#calendarDate.month + 1,
      0,
    ).getDate(); // 현재 달의 일 수를 알아냄

    const fragment = new DocumentFragment();

    for (let i = 0; i < numberOfDates; i++) {
      const dateEl = document.createElement('div');
      dateEl.classList.add('date');
      dateEl.textContent = i + 1; // 0 부터 for 문이 돌아가기 때문에 +1
      dateEl.dataset.date = i + 1; // data-date 라는 속성을 추가해준다.
      fragment.appendChild(dateEl); // fragment 객체 트리에 먼저 만들어줌
    }
    fragment.firstChild.style.gridColumnStart =
      new Date(this.#calendarDate.year, this.#calendarDate.month, 1).getDay() +
      1; // getDay()는 요일데이터를 index 번호로 데이터를 줌 그래서 +1 을 해야함
    this.calendarDatesEl.appendChild(fragment); // 찾아낸 달의 일 수 에 맞춰서 div 생성해줌 (day div)
  }
new Date() 매개변수에 대해서
1. Date() 메소드는 매개변수로 year, monthIndex, day, hours, minutes, seconds, milliseconds 를 받습니다.
2. 매개변수로 year, monthIndex, day 를 주었습니다.
3. year 은 현재 년도를 갖고왔습니다.
4. month 는 monthIndex 를 가져오게 되는데 day 를 0을 주게된다면, 전 달의 마지막 날을 가져오게 됩니다. 그래서 현재의 마지막 날을 얻기 위해서, month + 1 를 해주었고, day 는 0 을 주었습니다.

1. 12월의 마지막 날을 가져옴 2. 1월의 마지막 날을 가져옴

new DocumentFragment() ?
1. 메인 DOM의 조작(manipulation)이 필요할때 페이지 reflow 등 성능적 영향을 최소한으로 줄이기 위해 사용합니다.
2. Document Fragment를 루트 노드로 만든 DOM Tree를 사용하면, DOM과 관련된 작업을 페이지에 영향을 주지 않고 적용해서, 한번만 DOM 접근으로 적용할 수 있습니다.
getDay() 에 대해서
1. getDay() 는 요일의 데이터를 얻어올 수 있는 메소드
2. 요일을 month 와 같이 index 데이터로 준다.
3. 일요일 (0), 월요일 (1), 화요일 (2) ...

1. 2023.1.1 기준 요일 (일요일) 2. 2023.1.3 기준 요일 (화요일)

 

updateMonth() , updateDates() 를 toggle 할 때마다 호출

  toggleCalendar() {
    this.calendarEl.classList.toggle('active');
    this.updateMonth();
    this.updateDates();
  }

토요일 , 일요일 글씨 색상 바꾸기

 

colorSaturday()

  colorSaturday() {
    const saturdayEls = this.calendarDatesEl.querySelectorAll(
      `.date:nth-child(7n+${
        7 -
        new Date(this.#calendarDate.year, this.#calendarDate.month, 1).getDay()
      })`,
    );
    for (let i = 0; i < saturdayEls.length; i++) {
      saturdayEls[i].style.color = 'blue';
    }
  }
.date:nth-child(7n + ${7 - new Date(this.#calendarDate.year, this.#calendarDate.month, 1).getDay()} ?
- nth-child(숫자n) : n 은 0부터 시작된다.
- 아래와 같이 계산을 하면, 현재 달의 토요일이 몇번 째 인지 알 수 있음. ( 외우는게 답인듯 )

2023.1.1 일 기준으로 첫번째 날 요일은 0(일요일) 이라는 것을 알 수 있음
1. 7에서 0을 빼면 7이 나옴. 2. 7n ( 첫번째는 7*0 하면 0이 나옴) 3. 7n+위계산 = 0 + 7 = 7 (토요일은 7번째에 있음)

querySelectorAll 로 받아온 element 배열을 for문을 통해 하나씩 style 을 적용해준다.

 

colorSunday()

  colorSunday() {
    const sundayEls = this.calendarDatesEl.querySelectorAll(
      `.date:nth-child(7n + ${
        (8 -
          new Date(
            this.#calendarDate.year,
            this.#calendarDate.month,
            1,
          ).getDay()) %
        7
      })`,
    );
    for (let i = 0; i < sundayEls.length; i++) {
      sundayEls[i].style.color = 'red';
    }
  }
.date:nth-child( 7n + ${ (8 - new Date(this.#calendaerDate.year, this.#calendarDate.month, 1).getDay()) % 7)}) ?

2023.2.1 기준으로 첫째 날은 3(수요일)이라는 것을 알 수 있음
1. 8에서 3을 먼저 뺴줌 2. 5에서 7을 나눠줌 3. 7n ( 첫번째 0 ) + 5 하면 5가 나옴. 결과는 2월의 첫번째 일요일은 5일이라는 것을 알 수 있음.

 

일이 업데이트 될 때마다, 컬러 바꿔주기

  updateDates() {
	...
    this.colorSaturday(); // 토요일 색상 변경
    this.colorSunday(); // 일요일은 색상 변경
  }

현재 날짜 색상 바꾸기

 

markToday()

  markToday() {
    const currentDate = new Date();
    const currentMonth = currentDate.getMonth();
    const currentYear = currentDate.getFullYear();
    const today = currentDate.getDate();
    if (
      currentYear === this.#calendarDate.year &&
      currentMonth === this.#calendarDate.month
    ) {
      this.calendarDatesEl
        .querySelector(`[data-date='${today}']`)
        .classList.add('today');
    }
  }
current 데이터 변수들을 생성
if 문
- 현재 년도와 현재 달이 저장 된 캘런더 데이터의 년도와 달이 같을 경우
- data-date 속성에 today 변수에 들어간 현재 일 과 같은 data-date 가 있을 경우에 그 요소에게 "today" 라는 클래스를 추가한다

 

date 가 업데이트 될 때마다 현재 날짜의 배경 색상 변경해주기

  updateDates() {
	...
    this.markToday(); // 당일 색상 변경
  }

선택 된 날짜 배경 색상 변경하기

 

calendarDatesEl 에게 클릭 이벤트 주기

  addEvent() {
  	...
    this.calendarDatesEl.addEventListener(
      'click',
      this.onClickSelectDate.bind(this),
    );
  }

 

onClickSelectDate()

  onClickSelectDate(event) {
    const eventTarget = event.target; // 클릭한 요소를 eventTarget 변수에 담아주기
    if (eventTarget.dataset.date) {
      this.calendarDatesEl
        .querySelector('.selected')
        ?.classList.remove('selected');
      eventTarget.classList.add('selected');
      this.selectedDate = {
        data: new Date(
          this.#calendarDate.year,
          this.#calendarDate.month,
          eventTarget.dataset.date,
        ), // 현재 달력의 년도, 월 그리고 클릭한 요소의 data-date(일) 을 입력
        year: this.#calendarDate.year, // 현재 달력의 년도
        month: this.#calendarDate.month, // 현재 달력의 달
        date: eventTarget.dataset.date, // 클릭한 요소의 data-date(일)
      };
      this.calendarEl.classList.remove('active');
    }
  }
1. 클릭한 요소를 "eventTarget" 이라는 변수에 담아주기
2. eventTarget의 data 속성이 있을 경우에 .selected 라는 클래스를 갖고 있는 요소에게 클래스를 제거해주어 초기화해주기
3. 클릭한 요소 (eventTarget) 에게 selected 라는 클래스 주기
4. 미리 만들어놓은 selectedDate 데이터 객체에 데이터를 넣어주기
5. 선택하면 달력이 꺼지게 "active" 클래스를 지워준다.

 

markSelectedDate ()

  markSelectedDate() {
    if (
      this.selectedDate.year === this.#calendarDate.year &&
      this.selectedDate.month === this.#calendarDate.month
    ) {
      this.calendarDatesEl
        .querySelector(`[data-date='${this.selectedDate.date}']`)
        .classList.add('selected');
    }
  }
1. 선택된 년도와 달이 현재 년도와 달이 같을 경우에 calendarDatesEl 에서 data-date가 현재 선택된 날짜와 같은 값을 갖고 있는 아이에게 "selected" 라는 클래스를 추가해준다.

 

일을 업데이트할 때마다 선택된 날짜에 색상이 변경될 수 있도록 markSelectedDate () 를 호출해준다.

  updateDates() {
	...
    this.markSelectedDate(); // 선택 된 날짜 보여주기
  }

다음 버튼과 이전 버튼을 눌렀을 때 그 달의 데이터 보여주기

 

다음 버튼과 이전 버튼에게 클릭 이벤트 주기

  addEvent() {
	...
    this.nextBtnEl.addEventListener('click', this.moveToNextMonth.bind(this));
    this.prevBtnEl.addEventListener('click', this.moveToPrevMonth.bind(this));
	...
  }

 

moveToNextMonth()

  moveToNextMonth() {
    this.#calendarDate.month++;
    if (this.#calendarDate.month > 11) {
      this.#calendarDate.month = 0;
      this.#calendarDate.year++;
    }
    this.updateDates();
    this.updateMonth();
  }
1. 만들어놓은 달력 데이터의 달을 +1 씩 올려주기 ( 다음 달로 이동 )
2. 12월 보다 커질 경우에는 1월로 돌아오게 해주면서, 년을 1년을 추가해줘, 다음 년도의 1월로 오게 해준다.
3. 일과, 상단에 있는 달을 업데이트해준다.

 

moveToPrevMonth()

  moveToPrevMonth() {
    this.#calendarDate.month--;
    if (this.#calendarDate.month < 0) {
      this.#calendarDate.month = 11;
      this.#calendarDate.year--;
    }
    this.updateDates();
    this.updateMonth();
  }
1. 만들어놓은 달력 데이터의 달을 -1 씩 내려주기 ( 전의 달로 이동 )
2. 1월보다 적어질 경우에는, 1년 전으로 이동하며 12월로 이동하게 해준다.
3. 일과, 상단에 있는 달을 업데이트해준다.

처음 접속했을 시 input 에 현재 날짜 보여주기 + 선택된 날짜로 input 값 바꿔주기

 

setDateInput ()

  setDateInput() {
    this.dateInputEl.textContent = this.formateDate(this.selectedDate.data);
    this.dateInputEl.dataset.value = this.selectedDate.data;
  }
1. input 안에 선택된 날짜 or (선택되지 않았을 시) 현재 날짜를 동적으로 넣어준다.
2. input의 data 속성으로 value 를 넣어주는데, selectedDate 의 data 를 속성 값으로 넣어준다.

선택 하지않았을 때
선택 되었을 떄

 

formateDate()

  formateDate(dateData) {
    let date = dateData.getDate();
    if (date < 10) {
      date = `0${date}`;
    }
    let month = dateData.getMonth() + 1;
    if (month < 10) {
      month = `0${month}`;
    }

    let year = dateData.getFullYear();
    return `${year}/${month}/${date}`;
  }
1. 받아온 selectedDate의 data 에서 일을 받아오는데 10보다 작은 일수라면 앞에 0을 붙여준다. (01일 , 02일 등)
2. month 는 + 1을 하여, index 번호가 아닌 실제 달처럼 보이도록한다. 그리고 위와 같은 조건문을 걸어준다.
3. 선택 된 년도도 받아와준다
4. 이 값들을 백틱을 활용해 문자열로 반환해준다 (Return)
5. 반환 된 값은 dateInputEl (즉 input) 의 textContent 에 동적으로 기입된다.

 

처음에 접속 했을 시에, 현재 날짜를 input에 보여주기 위해 constructor에 호출해준다.

  constructor() {
	...
    this.setDateInput();
	...
  }

 

날짜를 선택했을 시에, input 에 선택된 날짜를 보여주기 위해 함수를 호출해준다.

onClickSelectDate()

  onClickSelectDate(event) {
	...
      this.setDateInput();
	...
    }
  }

달력을 껐다 켰을 시, 선택 된 날짜의 달력을 보여주기

 

1. toggleCalendar() 함수에서

  toggleCalendar() {
    if (this.calendarEl.classList.contains('active')) {
      this.#calendarDate = { ...this.selectedDate };
    } // 돌아다니다가, 다시 토글해서 킬 때 선택 된 날짜의 월 보여주기
    this.calendarEl.classList.toggle('active');
    this.updateMonth();
    this.updateDates();
  }

 

달력 클래스에 "active" 라는 클래스가 있을 시에 (즉, 캘린더가 켜질 때) 캘린더의 데이터를 선택된 날짜데이터로 바꿔줌
그래서 다시 들어갔을 때에 선택된 날짜의 달력을 보여줄 수 있도록함.

전체 기능 구현 코드