calendar
Updated:
FinalProject에서 calendar 기능을 구현할 때 https://codepen.io/peanav/pen/ulkof 를 가져와서 구현했다.
!function() {
// !function은 자기호출 익명함수이다.
// 함수가 만들어지자 마자 즉시 실행되며, 반환하는 것의 반대 값을 반환한다.(!반환값)
// 실제 반한되는 값을 얻으려면 !function(){}()이 아닌 (function(){})();을 써야한다.
// !function(){}('이 부분을 통해 매개변수를 받는다.')
var today = moment();
// moment.js는 자바 스크립트에서 dates를 다루기 위한 Swiss Army knife이다.
// 깔끔하고 간결한 API를 이용해서 날짜와 시간을 분석, 검증, 조작, 표시할 수 있다.
// <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js">
// </script>를 추가하면 사용할 수 있다.
function Calendar(selector, events) {
// selector : #calendar
// events : data(event가 저장되어 있다.)
this.el = document.querySelector(selector);
// new 키워드를 통해 생성자 함수를 만들었기 때문에 이 경우 this는 window가 아니라 빈 객체이고,
// this.el => 빈 객체에 el이라는 속성을 추가한다.
// (strict mode에서는 this에 디폴트 바인딩이 없기 때문에 undefined에러 발생)
this.events = events;
// events들을 this의 속성으로 추가한다.
this.current = moment().date(1);
// Sun Aug 01 2021 13:06:39 GMT+0900 현재 month에서 1일
this.draw(); // Calendar.prototype.draw 호출
var current = document.querySelector('.today');
if(current) {
var self = this;
window.setTimeout(function() {
self.openDay(current);
}, 500);
}
}
Calendar.prototype.draw = function() {
// Function.prototype 속성 : 이전에 정의된 객체 타입에 공유 속성을 추가한다.
// 이것은 객체 타입의 인스턴스 하나에만 적용되는 것이 아니라 이 함수로 생성하는 모든 객체와 공유한다.
//Create Header
this.drawHeader();
//Draw Month
this.drawMonth();
this.drawLegend();
}
Calendar.prototype.drawHeader = function() {
var self = this;
// this => Calendar {el: div#calendar, events: Array(16), current: f}
// Calendar 함수에서 this.el ... 으로 추가한 속성들이 들어가있음
if(!this.header) {
// this(Calendar)에 header라는 속성이 없다면 if문 실행
// Create the header elements
this.header = createElement('div', 'header');
// 기본적으로 새로운 요소를 추가하려면,
// this.header = document.createElement('div');
// this.header.className = 'header';
// 위와 같은 방법으로 추가 가능하지만 새롭게 createElement 함수를 생성하여
// function createElement(tagName, className, innerText)
// 간단하게 요소를 추가하였다.
this.header.className = 'header';
// header의 className을 header로 지정한다.
this.title = createElement('h1');
// this.title에 'h1'태그를 담는다. <h1></h1>
var right = createElement('div', 'right');
// <div class="right"></div>
right.addEventListener('click', function() { self.nextMonth(); });
// class name="right"인 요소를 클릭하면, Calendar.prototype.nextMonth 실행
var left = createElement('div', 'left');
// <div class="left"></div>
left.addEventListener('click', function() { self.prevMonth(); });
// class name="left"인 요소를 클릭하면, Calendar.prototype.prevMonth 실행
//Append the Elements
this.header.appendChild(this.title);
// header 요소에 title요소 추가
this.header.appendChild(right);
// header 요소에 right요소 추가
this.header.appendChild(left);
// header 요소에 left요소 추가
this.el.appendChild(this.header);
// this.el(#calendar) 요소에 header 요소 추가
}
this.title.innerHTML = this.current.format('MMMM YYYY');
// title의 html = this.current(현재 월의 1일).format(형식지정 => 'MMMM YYYYY');
}
Calendar.prototype.drawMonth = function() {
var self = this;
this.events.forEach(function(ev) {
ev.date = self.current.clone().date(Math.random() * (29 - 1) + 1);
});
// events Array에 담겨져 있는 수 만큼 반복실행하며
// 반복실행될 때마다 ev.date => events에 담겨진 각각의 event들의
// self.current.clone() => current를 복사한 것에 date(Math.random() * (29 - 1) + 1);
// Math.random() 실행시켜서 1일 ~ 29일 중 랜덤한 일자에 event를 넣는다.
// 예) ev => ev {eventName: "Lunch Meeting w/ Mark", calendar: "Work", color: "orange"}
// ev.date => Fri Aug 27 2021 12:50:21 GMT+0900 (한국 표준시)
// 8월 27일에 ev가 들어간다.
if(this.month) {
// this.month라는 변수가 true면 아래 if문 실행
this.oldMonth = this.month;
this.oldMonth.className = 'month out ' + (self.next ? 'next' : 'prev');
this.oldMonth.addEventListener('webkitAnimationEnd', function() {
self.oldMonth.parentNode.removeChild(self.oldMonth);
// oaddEventListener function}{ ... } 안에서 this는 이 함수 밖 this와 다르다.
// 우리가 원하는 this는 함수 밖 this이기 때문에 this를 self 변수에 담아서 함수 안에서 사용한다.
self.month = createElement('div', 'month');
// self.month = <div class='month'></div>
self.backFill();
// Calendar.prototype.backFill 호출
self.currentMonth();
// Calendar.prototype.currentMonth 호출
self.fowardFill();
// Calendar.prototype.fowardFill 호출
self.el.appendChild(self.month);
// self(this).el의 자식요소로 self(this).month요소를 추가
window.setTimeout(function() {
self.month.className = 'month in ' + (self.next ? 'next' : 'prev');
}, 16);
// self(this).month.className을 month in + next or prev로 설정(16/1000);
});
} else {
// this.month라는 변수가 false면 아래 else문 실행
this.month = createElement('div', 'month');
// this.month 변수에 <div class="month"></div>를 담았다.
this.el.appendChild(this.month);
// this.el.appendChild(this.month); this.el의 자식요소로 this.month를 설정한다.
this.backFill();
// Calendar.prototype.backFill 속성 호출
this.currentMonth();
// Calendar.prototype.currentMonth 호출
this.fowardFill();
// Calendar.prototype.fowardFill 호출
this.month.className = 'month new';
// backFill(), currentMonth(), forwardFill() 를 호출한 후에 month였던 className을 month new로 변경
}
}
Calendar.prototype.backFill = function() {
var clone = this.current.clone();
// clone 변수에 this.current(월의 1일 ex)2021-09-01)
var dayOfWeek = clone.day();
// dayOfWeek 변수에 clone(월의 1일).day() => .day()는 요일의 수 => 일(0), 월(1), 화(2), 수(3), 목(4), 금(5), 토(6)
if(!dayOfWeek) { return; }
// dayOfWeek이 있으면 아무것도 하지 않는다.
clone.subtract('days', dayOfWeek+1);
// clone(ex.2021-09-01).substract() => 해당 일에서 dayOfweek의 수만큼 'days' 일 단위로 뺀다.
for(var i = dayOfWeek; i > 0 ; i--) {
this.drawDay(clone.add('days', 1));
// 예) 2021-09-01에서 dayOfWeek은 3
// Calendar.prototype.drawDay를 호출하며
// clone.add('days', 1) => dayOfWeek 수만큼 'days' 일 단위로 더한다.
}
}
Calendar.prototype.fowardFill = function() {
var clone = this.current.clone().add('months', 1).subtract('days', 1);
// clone = this.current.clone() -> 현재 월의 1일 .add('months', 1) -> 다음 월의 1일
// .subtract('days', 1) -> 다음 월의 1일에서 하루를 빼면 현재 월의 마지막 날
var dayOfWeek = clone.day();
// dayOfWeek = clone.day() => 현재 월의 마지막 날의 요일(0~6)
if(dayOfWeek === 6) { return; }
// dayOfWeek이 6이면 토요일, 한 주가 끝난 것이기 때문에 아무것도 하지 않고 리턴
for(var i = dayOfWeek; i < 6 ; i++) {
this.drawDay(clone.add('days', 1));
// 만약 dayOfWeek이 6이 아닐경우 6이 될 때까지 days를 추가
// 예를 들어 8월인 경우 31일 화요일이 마지막 날이다.
// 이렇게 끝나면 너무 허전하니 9월 1일, 2일, 3일, 4일 토요일까지
// 보여주게 하는 것이 forwardFill이다.
}
}
Calendar.prototype.currentMonth = function() {
var clone = this.current.clone();
while(clone.month() === this.current.month()) {
// clone의 월과 this.current의 월이 달라질 때 False
this.drawDay(clone);
clone.add('days', 1);
// 다음 달로 넘어갈 때까지 하루씩 증가
}
}
Calendar.prototype.getWeek = function(day) {
if(!this.week || day.day() === 0) {
// this.week이 존재하지 않거나 or day.day() -> 인자 값의 요일 값이 0 이면 실행
this.week = createElement('div', 'week');
// this.week = <div className='week'></div>
this.month.appendChild(this.week);
// this.month의 자식 요소로 추가
}
}
Calendar.prototype.drawDay = function(day) {
var self = this;
this.getWeek(day);
//Outer Day
var outer = createElement('div', this.getDayClass(day));
// outer = <div className='this.getDayclass(day)로 받은 리턴값'></div>
outer.addEventListener('click', function() {
self.openDay(this);
// this => detail in
});
//Day Name
var name = createElement('div', 'day-name', day.format('ddd'));
// name = <div className='day-name'>day.format('ddd')<div>
// 여기서 day.format은 형식을 지정하는 것으로, 월요일이면 MON 화요일이면 TUE
//Day Number
var number = createElement('div', 'day-number', day.format('DD'));
// number = <div className='day-number'>day.format('DD')</div>
// day.format('DD') -> 31일이면 31
//Events
var events = createElement('div', 'day-events');
// events = <div className='day-events'></div>
this.drawEvents(day, events);
// Calendar.prototype.drawEvents 호출
outer.appendChild(name);
// day 요소에 자식요소로 추가
outer.appendChild(number);
// day 요소에 자식요소로 추가
outer.appendChild(events);
// day 요소에 자식요소로 추가
this.week.appendChild(outer);
// day 요소를 this.week 의 자식 요소로 추가(week 클래스 요소)
}
Calendar.prototype.drawEvents = function(day, element) {
if(day.month() === this.current.month()) {
// day.month() 와 this.current.month()가 같으면 실행
var todaysEvents = this.events.reduce(function(memo, ev) {
// todaysEvents 변수에 this.events.reduce() 함수 실행
if(ev.date.isSame(day, 'day')) {
// isSame은 momentjs에서 사용하는 함수로,
// 예) console.log(moment('2010-10-20').isSame('2010-10-21')); // false
// console.log(moment('2009-10-20').isSame('2010-10-21', 'year')); // false
memo.push(ev);
// memo.push(ev); 입력
// ev.date가 인자값인 day와 같으면 if문이 실행되고,
// memo라는 배열 변수에 해당 event가 들어간다.
}
return memo;
// 이런 형태로 리턴이 되면 todaysEvents 배열 변수 안에 memo가 담아진다
// 예) : day - Wed Sep 01 2021 18:31:40 GMT+0900 (한국 표준시)
// ev - {eventName: "Parent/Teacher Conference", ... date: f}
// memo - 0: {eventName: "Parent/Teacher Conference", ... date: f}
// length: 1
// todaysEvents - 0: {eventName: "Parent/Teacher Conference", ... date: f}
// length: 1
}, []);
todaysEvents.forEach(function(ev) {
var evSpan = createElement('span', ev.color);
// evSpan = <span className='ev.color'></span>
// ev.color는 랜덤으로 부여하고, className에 따라 색 부여
element.appendChild(evSpan);
});
}
}
Calendar.prototype.getDayClass = function(day) {
classes = ['day'];
if(day.month() !== this.current.month()) {
classes.push('other');
// 전 월 또는 다음 월이면 day other
} else if (today.isSame(day, 'day')) {
// 오늘이면 day today
classes.push('today');
}
return classes.join(' ');
}
Calendar.prototype.openDay = function(el) {
var details, arrow;
var dayNumber = +el.querySelectorAll('.day-number')[0].innerText || +el.querySelectorAll('.day-number')[0].textContent;
// dayNumber => 선택한 Day의 일수, 처음 실행했을땐 day today 값이 저장
var day = this.current.clone().date(dayNumber);
// day => 현재 시각 예) _d: Tue Aug 31 2021 20:45:32 GMT+0900 (한국 표준시) {}
var currentOpened = document.querySelector('.details');
// currentOpened 변수에 'details'의 클래스 이름을 가진 요소를 담음
//Check to see if there is an open detais box on the current row
if(currentOpened && currentOpened.parentNode === el.parentNode) {
// currentOpend and currentOpened의 부모 노드가
// el의 부모 노트와 같으면 if문 실행
// 부모 노드는 <div class="week">...</div>
details = currentOpened;
// details 변수에 currentOpend를 담는다.
// <div class='details in'>...</div>
arrow = document.querySelector('.arrow');
} else {
//Close the open events on differnt week row
//currentOpened && currentOpened.parentNode.removeChild(currentOpened);
// 아래 조건문은 event를 닫는 구문
if(currentOpened) {
currentOpened.addEventListener('webkitAnimationEnd', function() {
currentOpened.parentNode.removeChild(currentOpened);
});
currentOpened.addEventListener('oanimationend', function() {
currentOpened.parentNode.removeChild(currentOpened);
});
currentOpened.addEventListener('msAnimationEnd', function() {
currentOpened.parentNode.removeChild(currentOpened);
});
currentOpened.addEventListener('animationend', function() {
currentOpened.parentNode.removeChild(currentOpened);
});
currentOpened.className = 'details out';
// details in이었던 className을 details out로 변경
}
//Create the Details Container
details = createElement('div', 'details in');
//Create the arrow
var arrow = createElement('div', 'arrow');
//Create the event wrapper
details.appendChild(arrow);
el.parentNode.appendChild(details);
}
var todaysEvents = this.events.reduce(function(memo, ev) {
if(ev.date.isSame(day, 'day')) {
// ev.date() => 이벤트 날짜와 isSame(day, 'day') day의 'day'=> 날짜가 같으면 실행
memo.push(ev);
// memo 배열 변수에 ev를 push
}
return memo;
// todaysEvents가 리턴된 memo 값을 가진다.
}, []);
this.renderEvents(todaysEvents, details);
// todaysEvents : 해당(선택된) 일의 events를 담고 있다.
// 예) (2) [{…}, {…}]
// 0: {eventName: "School Play", calendar: "Kids", color: "yellow", date: f}
// 1: {eventName: "Ice Cream Night", calendar: "Kids", color: "yellow", date: f}
// details : <div class="details in">...</div>
arrow.style.left = el.offsetLeft - el.parentNode.offsetLeft + 27 + 'px';
}
Calendar.prototype.renderEvents = function(events, ele) {
//Remove any events in the current details element : 현재 세부 정보 요소에서 이벤트 제거
var currentWrapper = ele.querySelector('.events');
// var currentWrapper 변수에 ele.querySelector('.events');
var wrapper = createElement('div', 'events in' + (currentWrapper ? ' new' : ''));
// wrapper = <div className='events in + new or '' '>
events.forEach(function(ev) {
// events에 들어있는 ev를 하나씩 꺼내서,
var div = createElement('div', 'event');
var square = createElement('div', 'event-category ' + ev.color);
var span = createElement('span', '', ev.eventName);
div.appendChild(square);
div.appendChild(span);
wrapper.appendChild(div);
// 를 추가한다.
});
if(!events.length) {
// events.length가 0일때
var div = createElement('div', 'event empty');
// div = <div className='event empty'></div>
var span = createElement('span', '', 'No Events');
// span = <span>No Events</span>
div.appendChild(span);
wrapper.appendChild(div);
}
if(currentWrapper) {
// 현재 event 페이지
currentWrapper.className = 'events out';
// className을 event out으로 변경
currentWrapper.addEventListener('webkitAnimationEnd', function() {
// detail in event 애니메이션 종료
currentWrapper.parentNode.removeChild(currentWrapper);
ele.appendChild(wrapper);
});
currentWrapper.addEventListener('oanimationend', function() {
currentWrapper.parentNode.removeChild(currentWrapper);
ele.appendChild(wrapper);
});
currentWrapper.addEventListener('msAnimationEnd', function() {
currentWrapper.parentNode.removeChild(currentWrapper);
ele.appendChild(wrapper);
});
currentWrapper.addEventListener('animationend', function() {
currentWrapper.parentNode.removeChild(currentWrapper);
ele.appendChild(wrapper);
});
} else {
ele.appendChild(wrapper);
}
}
Calendar.prototype.drawLegend = function() {
var legend = createElement('div', 'legend');
// legend = <div className='legend'></div>
var calendars = this.events.map(function(e) {
return e.calendar + '|' + e.color;
}).reduce(function(memo, e) {
if(memo.indexOf(e) === -1) {
memo.push(e);
}
return memo;
}, []).forEach(function(e) {
var parts = e.split('|');
var entry = createElement('span', 'entry ' + parts[1], parts[0]);
legend.appendChild(entry);
});
this.el.appendChild(legend);
}
Calendar.prototype.nextMonth = function() {
// 다음 월
this.current.add('months', 1);
this.next = true;
this.draw();
}
Calendar.prototype.prevMonth = function() {
// 전 월
this.current.subtract('months', 1);
this.next = false;
this.draw();
}
window.Calendar = Calendar;
// Calendar 함수를 window 객체의 전역 함수로 지정한다.
// 이 과정을 거쳐야 !function(){}(); 밖에서도 Calendar 함수를 사용할 수 있다.
// 전역 변수 및 함수 = window 객체
function createElement(tagName, className, innerText) {
var ele = document.createElement(tagName);
if(className) {
ele.className = className;
}
if(innerText) {
ele.innderText = ele.textContent = innerText;
}
return ele;
}
}();
!function() {
var data = [
{ eventName: 'Lunch Meeting w/ Mark', calendar: 'Work', color: 'orange' },
{ eventName: 'Interview - Jr. Web Developer', calendar: 'Work', color: 'orange' },
{ eventName: 'Demo New App to the Board', calendar: 'Work', color: 'orange' },
{ eventName: 'Dinner w/ Marketing', calendar: 'Work', color: 'orange' },
{ eventName: 'Game vs Portalnd', calendar: 'Sports', color: 'blue' },
{ eventName: 'Game vs Houston', calendar: 'Sports', color: 'blue' },
{ eventName: 'Game vs Denver', calendar: 'Sports', color: 'blue' },
{ eventName: 'Game vs San Degio', calendar: 'Sports', color: 'blue' },
{ eventName: 'School Play', calendar: 'Kids', color: 'yellow' },
{ eventName: 'Parent/Teacher Conference', calendar: 'Kids', color: 'yellow' },
{ eventName: 'Pick up from Soccer Practice', calendar: 'Kids', color: 'yellow' },
{ eventName: 'Ice Cream Night', calendar: 'Kids', color: 'yellow' },
{ eventName: 'Free Tamale Night', calendar: 'Other', color: 'green' },
{ eventName: 'Bowling Team', calendar: 'Other', color: 'green' },
{ eventName: 'Teach Kids to Code', calendar: 'Other', color: 'green' },
{ eventName: 'Startup Weekend', calendar: 'Other', color: 'green' }
];
function addDate(ev) {
}
var calendar = new Calendar('#calendar', data);
// new operator : new 연산자는 사용자 정의 객체 타입 또는 내장 객체 타입의 인스턴스를 생성한다.
}();
Leave a comment