Language/JS

Calendar Script 정리 - (1)

JUNGKEUNG 2023. 12. 17. 15:14
window.onload = function () {

let today = new Date();
const calendarBody = document.querySelector('.calendar-body');
const prevEl = document.querySelector('.prev');
const nextEl = document.querySelector('.next');
const inputBox = document.querySelector('.input-box');
const inputBtn = document.querySelector('.input-btn');
const inputList = document.querySelector('.todoList');
const showList = document.querySelector('.showList');
const listText = document.querySelector('.listText');
const createDate = document.querySelector('.createDate');
const bgblack = document.querySelector('.bgblack');
const closedBtn = document.querySelector('.closed');
let currentDate;


buildCalendar();
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();
}

function showMain() {
  const mainDay = document.querySelector('.main-day');
  const mainDate = document.querySelector('.main-date');
  const dayList = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
  mainDay.innerHTML = dayList[today.getDay()];
  mainDate.innerHTML = today.getDate();
}

function makeElement(firstDate) {
  let weekly = 100;
  let dateSet = 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() || dateSet > pageYear[firstDate.getMonth()]) {
        // 만약 해당 칸에 날짜가 없으면 div엘리먼트만 생성한다.
        let dateEl = document.createElement('div');
        weeklyEl.appendChild(dateEl);
      } else {
        // 해당 칸에 날짜가 있으면 div엘리먼트 생성 후 해당 날짜 넣어주기
        let dateEl = document.createElement('div');
        dateEl.textContent = dateSet;
        dateEl.setAttribute('class', dateSet);
        dateEl.setAttribute('id', `${today.format2()}-${dateSet}`);
        weeklyEl.appendChild(dateEl);
        dateSet++;
      }
    }
    weekly++;
    calendarBody.appendChild(weeklyEl);
  }
  // 현재 내가 선택한 날짜가 있으면 이전 달, 다음 달로 넘어가도 화면에 보여주기 위해 써줌
  let clickedDate = document.getElementsByClassName(today.getDate());
  clickedDate[0].classList.add('active');
}

function removeCalendar() {
  let divEls = document.querySelectorAll('.calendar-body > #weekly > div');
  for (let i = 0; i < divEls.length; i++) {
    divEls[i].remove();
  }
}

// 왼쪽에 현재 날짜 업데이트 해주기.
function showMain() {
  const mainDay = document.querySelector('.main-day');
  const mainDate = document.querySelector('.main-date');
  const dayList = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
  mainDay.innerHTML = dayList[today.getDay()];
  mainDate.innerHTML = today.getDate();
}

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()
});

function currentDateget() {
  // format()을 이용해서 현재 날짜를 보기좋게 출력해주기 위해 사용.
  currentDate = today.format();
}

calendarBody.addEventListener('click', function (e) {
  let target = e.target;
  let eachDate = document.querySelectorAll('.calendar-body > #weekly > div');
  if (e.target.innerHTML === '') return;
  for (let i = 0; i < eachDate.length; i++) {
    eachDate[i].classList.remove('active');
  }
  target.classList.add('active');
  today = new Date(today.getFullYear(), today.getMonth(), target.innerHTML);
  showMain();
  currentDateget();
  redrawLi();
  resetInsert();
});

inputBtn.addEventListener('click', function (e) {
  e.preventDefault();
  let inputValue = inputBox.value;
  insertTodo(inputValue);
});

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 = '';
}

function redrawLi() {
  // 다른 날짜를 클릭했을때 그 전에 작성한 totolist목록을 먼저 다 지우기 위해 li와 span을 찾아와 for문으로 지워주고 다시 그려준다.
  let liEl = document.querySelectorAll('LI');
  for (let i = 0; i < liEl.length; i++) {
    inputList.removeChild(liEl[i]);
  }
  for (let todoList in DATA) {
    if (todoList === currentDate) {
      for (let i = 0; i < DATA[todoList].length; i++) {
        const liEl2 = document.createElement('li');
        const spanEl2 = document.createElement('span');
        const delBtn2 = document.createElement('button');
        delBtn2.innerText = "DEL";
        delBtn2.setAttribute('class', 'del-data');
        spanEl2.innerHTML = DATA[todoList][i].todo;
        liEl2.appendChild(spanEl2);
        liEl2.appendChild(delBtn2);
        inputList.appendChild(liEl2);
        liEl2.setAttribute('id', DATA[todoList][i].id);
        delBtn2.addEventListener('click', delWork);
        liEl2.addEventListener('dblclick', showTodo);
      }
    }
  }
}

// 다음달,이전달 다른날, 첫 로드 될 때 마다 todo 목록이 있으면(if로 조건문 처리) 다 지우고 다시 그려주는 함수
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);
      }
    });
  }
}
resetInsert();

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();
}

function showTodo(e){
  showList.style.display = "block"
  bgblack.style.display = "block"
  listText.textContent = e.target.textContent;
  createDate.textContent = currentDate;
}

closedBtn.addEventListener('click', function(e){
  showList.style.display = "none";
  bgblack.style.display = "none";
});

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

}

 

위에 소스가 어떻게 동작되는지 파악이 안되어 분석 및 정리 하고자 작성을 해보았다.

 

window.onload = function()

HTML 문서는 객체 태그들을 위에서부터 아래로 차례로 읽어 들인다. 그런데 이러한 특성으로 인해 가끔 자바스크립트의 작성 위치에 따라 오작동을 일으키기도 한다.

 

예) <script> 태그의 자바스크립트에서 태그의 id가 'name'인 엘리먼트 요소를 가져와 색깔을 파란새긍로 바꿔주는데, 가져오려는 <p id="name">hello</p> 엘리먼트가 <script> 태그 밑에 위치해 있을 경우 다음과 같이 문제가 발생한다.

<html>
    <body>
        <script>
            let a = document.getElementById('name');
            a.style.color = "blue"
        </script>

        <p id="name">hello</p>
    </body>
</html>

 

이러한 까닭은 HTML은 실행 이전에 에러 체크를 하지 않고 실행을 하는 인터프리터 언어적 특성으로 인해, 자바스크립트의 document.getElementById('name')이 html 내부 id가 name 이란 태그가 생성되기도 전에 실행되므로 요소를 가져올 수가 없기 때문에 문제가 발생한다. 

 

그러므로 아래와 같이 자바스크립트 태그를 문서의 뒤로 옮겨야만 하는데, 문제가 해결되기는 하지만 html 문서가 길어진다면, 자바스크립가 아래쪽에 놓여있다면 휠을 내리기도 귀찮고 보기에도 안좋아진다.

 

그렇기에 자바스크립트가 문서가 준비된 상황 이후에 발동하도록만 한다면 문서 앞에 선언해도 상관 없어지는데, 바로 이런 것을 해주는 것이 window.onload() 메소드 인 것이다. 웹 브라우저 자체를 담당하는 window 라는 객체가 웹 문서를 불로올때 문서가 사용되는 시점에 실행되는 onload 라는 함수를 내가 다시 재정의 한다는 개념이다.

 

window.onload 문제점


하지만 window.onload() 메소드는 오로지 한번만 정의할 수 있다는 한계점이 존재한다. 만일 다른 위치의 <script> 태그에서 메소드를 사용하려고 한다면, 이미 위에서 한번 정의했다면 중복 처리가 되어 분담 사용이 불가능하다. 

따라서 아래와 같이 addEventListener() 메서드를 통해 load 이벤트를 받는 식으로 구성하면 얼마든지 중복해서 사용이 가능하다.

 

이벤트란?


  • 사용자와 상호 작용을 하면서 마우스(키보드, 터치, 펜 등)를 조작하면 그에 대한 반응을 하는 것
  • 특정 이벤트가 발생되었을때, 특정 함수를 실행할 수 있게 해주는 것이 addEventListener 이다.

 

자바스크립트 이벤트 처리 방법 3가지


1. HTML 요소의 속성으로 등록

  • 이벤트를 단 하나밖에 지정할 수 없다는 단점이 있다.
  • <input type="button" onclick="btnClick()">

2. DOM 요소의 프로퍼티로 등록

  • 위의 동일 단점이 있다.
  • let btn = document.getElementById("button"); btn.onclick = btnClick();

3. addEventListener 사용하여 등록

  • 여러 개의 이벤트를 등록할 수 있다.

 

AddEventListener("이벤트")


기본 문법

const title = document.querySelector("h1");

title.addEventListener("이벤트종류", 함수이름)

//반대도 있다.
title.removeEventListener('이벤트종류','함수이름');
  • title이라는 변수가 가만히 있다가 click라는 이벤트가 일어났을 때 반응한다.
  • 클릭이 일어났을 때 일어날 행동을 함수로 만들어줘서 두 번째 인수에다 추가해준다.

 

다양한 이벤트

이벤트 명 설명
mouseover 해당 객체의 영역 위에 마우스 커서가 진입하는 순간
mouseout 해당 객체의 영역 위에 마우스 커서가 빠져나가는 순간
mousedown 해당 객체의 영역 위에서 마우스 버튼을 누르는 순간
mouseup 해당 객체의 영역 위에서 마우스 버튼을 떼는 순간
mousemove 해당 객체의 영역 위에서 마우스 커서가 움직이는 순간
keydown 키를 눌렀을 때 발생
keyup 키를 뗐을 때 발생
keypress 키를 눌렀을 때 발생 (잘 안 쓰임)
click 마우스 버튼을 클릭하고 버튼에서 손가락을 떼면 발생
resize  브라우저 창의 크기를 조절할때 발생한다.
scroll  스크롤바를 드래그하거나 키보드(up, down)를 사용하거나 마우스 휠을 사용해서 웹페이지를 스크롤 할 때 발생
change 변동이 있을 때 발생
focus 포커스를 얻었을 때 발생
load 로드가 완료 되었을 때 발생
select option 태그 등에서 선택을 했을 때 발생
submit submit 실행 시 발생

 

출처


https://inpa.tistory.com/entry/JS-📚-windowonload-정리

https://lakelouise.tistory.com/35   - 이벤트 등록, addEventListener() 함수