나홀로 메모장 파생형 만들기-구성 및 사이드바 달력

Reference

https://bigtop.tistory.com/63 ([JavaScript] 일반적인 달력 만들기 – HTML 뼈대 잡기)

https://opentutorials.org/course/1375/6761 (생활코딩 addEventListener())


이거는 내가 직접적으로 올린적은 없는데, 내일배움단 하면서 만들었던거다. 근데 나는 영화도 안보고 사이트 스크랩도 잘 안하는데 이건 사이트 URL이 있어야 하는 거기도 하고… 영화 좋아하거나 독서록 이런거 쓸 때는 좋음. 근데 나는 안써요. 애초에 마지막으로 본 영화가 명탐정 피카츄인 시점에서 말 다했지. 그래서 새로 만들었음.

일단 기본적인 구성은 이렇다.

컨텐츠는 그리드뷰에 페이지네이션이 사용될 예정이고, 한 페이지에 보여줄 컨텐츠는 3의 배수(3n)개이다. 9개 12개 이런 식. 글에는 제목과 내용이 있고, 링크는 나중에 더보기 버튼이 되거나 빠질 예정이다. 또한 달력이 있는 오른쪽은 사이드바인데… 그렇다. 오늘 만들 게 저 달력이다.

우효 www 멋진 달력이제 www 그런데 이걸 할 수 있어요? ㅇㅇ 생각보다 간단함. 아니 농담 아니고 진짜입니다. 일단 달력을 만드는 순서는 다음과 같다.

  1. HTML 뼈대 잡기
  2. 날짜 가져오기
  3. 달력 렌더링하고 모양 잡기
  4. 버튼 기능 추가 및 이전달/다음달 날짜 구별하기

어때요? 참 쉽죠? (대충 밥아저씨 짤)

<div class="card" id="calendar">
	<div class="card-header">
		 Calendar
	</div>
	<h5 class="card-title" id="month"></h5>
	<div class="btn-group" role="group" aria-label="Basic outlined example">
		<button type="button" id="prevmonth" class="btn btn-outline-primary">이전달</button>
		<button type="button" id="gotoday" class="btn btn-outline-primary">오늘</button>
		<button type="button" id="nextmonth" class="btn btn-outline-primary">다음달</button>
	</div>
	<div class="calendar-area">
		<div class="days">
			<div class="day">
				일
			</div>
			<div class="day">
				월
			</div>
			<div class="day">
				화
			</div>
			<div class="day">
				수
			</div>
			<div class="day">
				목
			</div>
			<div class="day">
				금
			</div>
			<div class="day">
				토
			</div>
		</div>
		<div class="dates">
		</div>
	</div>
</div>

뼈대는 사실 그렇게 어려울 게 없다. 왜냐하면 부트스트랩에서 다 갖고왔거든… 위 이미지를 보면 달력을 구성하고 있는 것은

  1. 달력(Calendar)
  2. 현재 몇월인가
  3. 이전달/오늘/다음달 버튼
  4. 요일
  5. 날짜

이렇게 다섯가지이다. 하지만 뼈대를 보면… 응? 저기 뭐가 빠졌는데요??? 아, 지금 뼈대에서 빠져있는 부분은 자바스크립트가 알아서 할 거니까 패스하셔도 됩니다. 잘~ 따라오면 날짜도 동적으로 뿅 나올거임.

#calendar {
    grid-column: 4/span 1;
    grid-row: 2/span 1;
}

#month {
    margin: 20px 0px;
    font-size: 1.4em;
    text-align: center;
    border-bottom: 2px dotted var(--main-color);
}

.calendar-area {
    margin: 15px auto;
    font-size: 1.3em;
}

.days {
    display: flex;
    border-bottom: 1px solid var(--sub-color);
    justify-content: space-evenly;
    text-align: center;
    margin-bottom: 15px;
}

.dates {
    display: flex;
    flex-flow: row wrap;
    justify-content: space-evenly;
    text-align: center;
}

.day, .date {
    width: calc(100% / 7);
    padding: 5px;
}

.date span {
    width: 50px;
}

.day:nth-child(7n + 1),
.date:nth-child(7n + 1) {
    color: var(--red);
}

.day:nth-child(7n),
.date:nth-child(7n) {
    color: var(--blue);
}

.other {
    opacity: .5;
}

.today {
    color: var(--sub-color);
    border-bottom: 2px double var(--main-color);
}

button.btn.btn-outline-primary, button.btn.btn-primary {
    border: 0;
    color: var(--main-color);
}

button.btn.btn-outline-primary:hover, button.btn.btn-primary:hover {
    color: var(--sub-color);
    background-color: transparent;
    border-radius: 0;
}

스타일시트는 이런 식으로 줬다.

let date = new Date();

const renderCalendar = () => {
    const viewYear = date.getFullYear();
    const viewMonth = date.getMonth();

    const prevMonth = new Date(viewYear, viewMonth, 0);
    const thisMonth = new Date(viewYear, viewMonth + 1, 0);
    const prevDate = prevMonth.getDate();
    const prevDay = prevMonth.getDay();
    const thisDate = thisMonth.getDate();
    const thisDay = thisMonth.getDay();

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

    document.querySelector('#month').textContent = `${viewYear}년 ${viewMonth + 1}월`;

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

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

    const dates = prevDates.concat(thisDates, nextDates);

    const firstDate = dates.indexOf(1);
    const lastDate = dates.lastIndexOf(thisDate);
    dates.forEach((date, i) => {
        const condition = i >= firstDate && i < lastDate + 1
            ? 'this'
            : 'other';
        dates[i] = `<div class="date"><span class="${condition}">${date}</span></div>`;
    })

    document.querySelector('.dates').innerHTML = dates.join('');

    const today = new Date();
    if (viewMonth === today.getMonth() && viewYear === today.getFullYear()) {
        for (let date of document.querySelectorAll('.this')) {
            if ((+date.innerText) === today.getDate()) {
                date.classList.add('today');
                break;
            }
        }
    }

}

const prevMonth = () => {
    date.setMonth(date.getMonth() - 1);
    renderCalendar();
}

const nextMonth = () => {
    date.setMonth(date.getMonth() + 1);
    renderCalendar();
}

const goToday = () => {
    date = new Date();
    renderCalendar();
}

prevMonth_button = document.querySelector('#prevmonth');
nextMonth_button = document.querySelector('#nextmonth');
goToday_button = document.querySelector('#gotoday');

prevMonth_button.addEventListener('click', (e) => {
    prevMonth();
})

nextMonth_button.addEventListener('click', (e) => {
    nextMonth();
})

goToday_button.addEventListener('click', (e) => {
    goToday();
})

renderCalendar();

이건 JS 코드. 아니 이걸로 달력이 돼요??? 예 됩니다.

let date = new Date();

const renderCalendar = () => {
    const viewYear = date.getFullYear();
    const viewMonth = date.getMonth();

    const prevMonth = new Date(viewYear, viewMonth, 0);
    const thisMonth = new Date(viewYear, viewMonth + 1, 0);
    const prevDate = prevMonth.getDate();
    const prevDay = prevMonth.getDay();
    const thisDate = thisMonth.getDate();
    const thisDay = thisMonth.getDay();

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

    document.querySelector('#month').textContent = `${viewYear}년 ${viewMonth + 1}월`;

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

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

    const dates = prevDates.concat(thisDates, nextDates);

    const firstDate = dates.indexOf(1);
    const lastDate = dates.lastIndexOf(thisDate);
    dates.forEach((date, i) => {
        const condition = i >= firstDate && i < lastDate + 1
            ? 'this'
            : 'other';
        dates[i] = `<div class="date"><span class="${condition}">${date}</span></div>`;
    })

    document.querySelector('.dates').innerHTML = dates.join('');

    const today = new Date();
    if (viewMonth === today.getMonth() && viewYear === today.getFullYear()) {
        for (let date of document.querySelectorAll('.this')) {
            if ((+date.innerText) === today.getDate()) {
                date.classList.add('today');
                break;
            }
        }
    }

}

이 부분에 달력 렌더링의 정수가 담겨져 있다.

let date = new Date();는 이게 있어야 날짜 정보를 가져올 수 있기 때문에 필요하다. 이거 있으면 시간도 가져옵니다. 그게 무슨 말이냐… 시계를 만들 수 있음. 

const viewYear = date.getFullYear();
const viewMonth = date.getMonth();

const prevMonth = new Date(viewYear, viewMonth, 0);
const thisMonth = new Date(viewYear, viewMonth + 1, 0);
const prevDate = prevMonth.getDate();
const prevDay = prevMonth.getDay();
const thisDate = thisMonth.getDate();
const thisDay = thisMonth.getDay();

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

위쪽은 걍 보는거다. 그리고 이전달, 이번달, 다음달에 해당하는 변수와 배열이 있는데 중요한 건

이전달: 이번달의 1일 이전 날짜를 남는 칸 수만큼 거꾸로 채운다.
이번달: 다 채운다.
다음달: 이번달 말일부터 날짜를 남는 칸 수만큼 앞으로 채운다.

이거다. 당연하게도 그래서 이전 달에 대한 정보가 필요한데, 이전 달이 몇월이고 말일이 무슨 요일인가? 를 알아야 채울 수 있다.

document.querySelector('#month').textContent = `${viewYear}년 ${viewMonth + 1}월`;

이 코드는 지금 몇월인지를 가져와서 텍스트로 만든 다음 채워주는 코드다.

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

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

const dates = prevDates.concat(thisDates, nextDates);
const firstDate = dates.indexOf(1);
const lastDate = dates.lastIndexOf(thisDate);

dates.forEach((date, i) => {
        const condition = i >= firstDate && i < lastDate + 1
            ? 'this'
            : 'other';
        dates[i] = `<div class="date"><span class="${condition}">${date}</span></div>`;
    })

document.querySelector('.dates').innerHTML = dates.join('');

이전 달 같은 경우, 이번달 1일이 일요일이면 굳이 채울 이유가 없다. 다르게 말하자면, 이전달 말일이 토요일이면 굳이 채우지 않아도 된다. 그래서 마지막달이 토요일이 아닐 경우이면 PrevDates라는 배열에 거꾸로 하나씩 넣어라, 이런 얘기다. 이번달은 1일이 월요일이므로 저번달 31일(일요일)만 끼워넣으면 된다. 다음달은 남는 칸만큼 끼워넣으면 되기때문에 1, 2, 3일이 들어간 것. 이번 달 31일은 수요일이다.

ForEach문에 있는 건 이번달과 다른 달의 날짜를 구별해 클래스를 부여하기 위한 코드인데 ?랑 :는 모르겠고… 이번달 날짜면 this, 아니면 other 클래스를 주게 된다. 그리고 other 클래스에 투명도를 부여하면 이전달, 다음달 날짜의 투명도가 바뀐다.

const today = new Date();
if (viewMonth === today.getMonth() && viewYear === today.getFullYear()) {
    for (let date of document.querySelectorAll('.this')) {
        if ((+ date.innerText) === today.getDate()) {
            date.classList.add('today');
            break;
        }
    }
}

이건 오늘 날짜 찾는 코드이다. 그리고 today라는 클래스를 부여하게 되면 오늘 날짜만 다르게 표시할 수 있다.

const prevMonth = () => {
    date.setMonth(date.getMonth() - 1);
    renderCalendar();
}

const nextMonth = () => {
    date.setMonth(date.getMonth() + 1);
    renderCalendar();
}

const goToday = () => {
    date = new Date();
    renderCalendar();
}

달력에 딸려오는 버튼들에 대한 기능이다. 지금 8월 29일인데 이전달이면 7월, 다음달이면 9월을 기준으로 렌더링하게 된다. renderCalendar()는 아까 설명했던 달력 그리는 함수.

prevMonth_button = document.querySelector('#prevmonth');
nextMonth_button = document.querySelector('#nextmonth');
goToday_button = document.querySelector('#gotoday');

prevMonth_button.addEventListener('click', (e) => {
    prevMonth();
})

nextMonth_button.addEventListener('click', (e) => {
    nextMonth();
})

goToday_button.addEventListener('click', (e) => {
    goToday();
})

renderCalendar();

함수는 그냥 짜기만 하면 작동하는건지 아닌건지 모른다. 그래서 맨 밑의 줄처럼 renderCalendar() 함수는 하단에 소환했고, 이전달/다음달/오늘 날짜로 돌아가는 버튼에는 이벤트 리스너로 함수를 연결했다.

좀 짤리긴 했지만 아무튼 잘 된다.