JavaScrpt/시간 날짜 달력

JavaScript Date - 달력 생성 함수 모듈화 4 (data 속성, 이벤트 버블링, 스크립트 파일)

carrotweb 2022. 4. 9. 17:05
728x90
반응형

달력 생성 함수를 공용으로 사용하기 위해 웹 페이지에서 정보를 가져와 사용하는 문제점을 해결하고 확장해서 사용하기 위해서입니다.

data-date 속성 추가

달력에서 날짜인 <td> 태그의 data-date 속성으로 연월일(yyyy-MM-dd)을 추가합니다.

// 날짜
var calendarDay = 0;
for (var index1 = 0; index1 < calendarWeekCount; index1++) {
	html += "<tr>";
	for (var index2 = 0; index2 < 7; index2++) {
		html += "<td"; --> 수정
		if (calendarMonthStartDay <= calendarPos && calendarDay < calendarMonthLastDate) {
			calendarDay++;
			html += " data-date=\"" + calendarYear + "-" + (calendarMonth < 10 ? "0" : "") + calendarMonth + "-" + (calendarDay < 10 ? "0" : "") + calendarDay +  "\">"; --> 추가
			html += "<span";
			... (중간 생략)
			html += ">" + calendarDay + "</span>";
		} else {
			html += ">"; --> 추가
		}
		html += "</td>";
		calendarPos++;
	}
	html += "</tr>";
}

 

<td> 태그에 data-date 속성을 추가한 이유는 달력에서 날짜 클릭 이벤트 처리 시 달력의 연도와 월을 별도로 가져오는 문제점과 <span> 태그의 텍스트를 읽어 날짜를 처리하는 문제점을 해결하기 위해서입니다.

// 날짜 클릭
$(document).on("click", ".calendar table > tbody > tr > td > span", function(event) {
	var yearmonth = $(".calendar-yearmonth").text().split(".");
	alert(yearmonth[0] + "." + yearmonth[1] + "." + $(event.target).text());
});

 

날짜인 <td> 태그 안에 다양한 태그들이 추가되어 정보들이 노출될 수 있기 때문에 <span> 태그로 한정하기에는 문제가 있습니다. 그래서 날짜 클릭 이벤트의 대상을 <span> 태그에서 부모인 <td> 태그로 변경하기 위해 on() 메서드에서 selector를 ".calendar table > tbody > tr > td > span"에서 ".calendar table > tbody > tr > td"로 변경합니다.

이벤트 버블링(Event Bubbling)

달력 생성 함수를 확장하여 날짜인 <td> 태그 안에 다양한 정보들을 추가하면 태그들이 추가될 겁니다.

<td>
	<span class="today" title="삼일절">1</span>
	<div class="holiday">삼일절</div>
</td>

그리고 추가된 정보를 클릭하였을 때 이벤트를 처리하기 위해 클릭 이벤트를 추가하게 될 겁니다.

그러면 <td> 태그 안의 다양한 태그들로부터 이벤트 버블링(Event Bubbling)이 발생합니다.

// 날짜 클릭
$(document).on("click", ".calendar table > tbody > tr > td", function(event) {
	console.log("td event : " + event.target.tagName);
});

// 날짜안 정보 클릭
$(document).on("click", ".calendar table > tbody > tr > td > div", function(event) {
	console.log("div event : " + event.target.tagName);
});

 

날짜를 클릭하면 <SPAN> 태그에서 이벤트가 발생됩니다.

td event : SPAN

 

날짜가 아닌 영역이나 빈 날짜를 클릭하면 <td> 태그에서 이벤트가 발생됩니다.

td event : TD

 

날짜 안에 있는 삼일절(공휴일)을 클릭하면 <div> 태그에서 이벤트가 발생한 후 <td> 태그에서 이벤트가 발생됩니다.

div event : DIV
td event : DIV

 

 

이처럼 <td> 태그 안의 <div> 태그에서 클릭 이벤트가 발생하면 이벤트가 부모로 전달되는 것을 이벤트 버블링(Event Bubbling)이라고 합니다.

이벤트가 부모 태그로 전파되는 것을 막기 위해 event 객체의 stopPropagation() 메서드를 사용하면 됩니다.

// 날짜 클릭
$(document).on("click", ".calendar table > tbody > tr > td", function(event) {
	console.log("td event : " + event.target.tagName);
	event.stopPropagation();
});

// 날짜안 정보 클릭
$(document).on("click", ".calendar table > tbody > tr > td > div", function(event) {
	console.log("div event : " + event.target.tagName);
	event.stopPropagation();
});

 

날짜 안에 있는 삼일절(공휴일)을 클릭하면 <div> 태그에만 이벤트가 발생됩니다.

div event : DIV

 

날짜 클릭 이벤트에서 <span> 태그가 아닌 data-date 속성이 있는 <td> 태그를 찾기 위해서는 현재 이벤트의 target(<span>)에서 부모 요소(<td>)를 찾아야 합니다.

parentElement는 현재 노드의 부모 노드를 반환합니다.

event.target --> <span>
event.target.parentElement --> <td>

 

<span> 태그 안에도 태그들이 추가될 수 있기 때문에 while() 문을 이용하여 부모 태그 명인 TD가 나올 때까지 반복해서 찾습니다. 그리고 <td> 태그의 data-date 속성을 읽어오게 수정합니다.

// 날짜 클릭
$(document).on("click", ".calendar table > tbody > tr > td", function(event) {
	event.stopPropagation();
	var eventTarget = event.target;
	while (eventTarget.tagName != "TD") {
		eventTarget = eventTarget.parentElement;
	}
	if ($(eventTarget).attr("data-date") != undefined) {
		alert($(eventTarget).attr("data-date"));
	}
});

 

오늘 날짜와 공휴일의 같을 경우 공휴일 툴팁이 나오지 않는 문제점을 해결하기 위해 툴팁을 분리하여 처리합니다.

html += "<span";
if (options.showToday && calendarYear == today.getFullYear() && calendarMonth == today.getMonth() + 1
	&& calendarDay == today.getDate()) {
	html += " class=\"today\"";
} else {
	var holiday = false;
	if (Object.keys(hashmapHoliday).length > 0) {
		// 공휴일(임시 공휴일 포함)
		var holidayInfo = hashmapHoliday[calendarYear + "-" + calendarMonth + "-" + calendarDay];
		if (holidayInfo == undefined || holidayInfo == null) {
			holidayInfo = hashmapHoliday[calendarMonth + "-" + calendarDay];
		}
		if (holidayInfo != undefined && holidayInfo != null) {
			html += " class=\"holiday\"";
			holiday = true;
		}
	}
	if (!holiday) {
		if (index2 == 0) {
			html += " class=\"sunday\"";
		} else if (index2 == 6) {
			html += " class=\"saturday\"";
		}
	}
}
if (Object.keys(hashmapHoliday).length > 0) {
	// 공휴일(임시 공휴일 포함)
	var holidayInfo = hashmapHoliday[calendarYear + "-" + calendarMonth + "-" + calendarDay];
	if (holidayInfo == undefined || holidayInfo == null) {
		holidayInfo = hashmapHoliday[calendarMonth + "-" + calendarDay];
	}
	if (holidayInfo != undefined && holidayInfo != null) {
		html += " title=\"" + holidayInfo.title + "\"";
	}
}
html += ">" + calendarDay + "</span>";

 

 

 

스크립트 파일 생성

달력 생성 함수를 공용으로 사용하기 위해 calendar.js 파일로 분리합니다.

function calendarHTML(date, options) {
	// 데이터 검증
	if (date == undefined || date == null || typeof date != "object" || !date instanceof Date) {
		return "";
	}
	
	// 기본값 처리
	if (options.showDay == undefined || options.showDay == null || typeof options.showDay != "boolean") {
		options.showDay = true;
	}
	if (options.showFullDayName == undefined || options.showFullDayName == null || typeof options.showFullDayName != "boolean") {
		options.showFullDayName = false;
	}
	if (options.showToday == undefined || options.showToday == null || typeof options.showToday != "boolean") {
		options.showToday = true;
	}

	// 공휴일
	var hashmapHoliday = [];
	hashmapHoliday["1-1"] = {"title" : "새해"};
	hashmapHoliday["3-1"] = {"title" : "삼일절"};
	hashmapHoliday["5-5"] = {"title" : "어린이날"};
	hashmapHoliday["6-6"] = {"title" : "현충일"};
	hashmapHoliday["8-15"] = {"title" : "광복절"};
	hashmapHoliday["10-3"] = {"title" : "개천절"};
	hashmapHoliday["10-9"] = {"title" : "한글날"};
	hashmapHoliday["12-25"] = {"title" : "성탄절"};
	
	if (options.arHoliday != undefined && options.arHoliday != null && Array.isArray(options.arHoliday)) {
		Object.assign(hashmapHoliday, options.arHoliday);
	}
	
	// 요일
	var days = ["일", "월", "화", "수", "목", "금", "토"];
	
	// 달력 연도
	var calendarYear = date.getFullYear();
	// 달력 월
	var calendarMonth = date.getMonth() + 1;
	// 달력 일
	var calendarToday = date.getDate();
	
	var monthLastDate = new Date(calendarYear, calendarMonth, 0);
	// 달력 월의 마지막 일
	var calendarMonthLastDate = monthLastDate.getDate();
	
	var monthStartDay = new Date(calendarYear, date.getMonth(), 1);
	// 달력 월의 시작 요일
	var calendarMonthStartDay = monthStartDay.getDay();
	
	// 주 카운트
	var calendarWeekCount = Math.ceil((calendarMonthStartDay + calendarMonthLastDate) / 7);
	
	// 오늘
	var today = new Date();
	
	var html = "";
	html += "<table>";
	if (options.showDay) {
		html += "<thead><tr>";
		for (var index = 0; index < days.length; index++) {
			html += "<th";
			if (index == 0) {
				html += " class=\"sunday\"";
			} else if (index == 6) {
				html += " class=\"saturday\"";
			}
			html += ">" + days[index];
			if (options.showFullDayName) {
				html += "요일";
			}
			html += "</th>";
		}
		html += "</tr></thead>";
	}
	html += "<tbody>";
	// 위치
	var calendarPos = 0;
	// 날짜
	var calendarDay = 0;
	for (var index1 = 0; index1 < calendarWeekCount; index1++) {
		html += "<tr>";
		for (var index2 = 0; index2 < 7; index2++) {
			html += "<td";
			if (calendarMonthStartDay <= calendarPos && calendarDay < calendarMonthLastDate) {
				calendarDay++;
				html += " data-date=\"" + calendarYear + "-" + (calendarMonth < 10 ? "0" : "") + calendarMonth + "-" + (calendarDay < 10 ? "0" : "") + calendarDay +  "\">";
				html += "<span";
				if (options.showToday && calendarYear == today.getFullYear() && calendarMonth == today.getMonth() + 1
					&& calendarDay == today.getDate()) {
					html += " class=\"today\"";
				} else {
					var holiday = false;
					if (Object.keys(hashmapHoliday).length > 0) {
						// 공휴일(임시 공휴일 포함)
						var holidayInfo = hashmapHoliday[calendarYear + "-" + calendarMonth + "-" + calendarDay];
						if (holidayInfo == undefined || holidayInfo == null) {
							holidayInfo = hashmapHoliday[calendarMonth + "-" + calendarDay];
						}
						if (holidayInfo != undefined && holidayInfo != null) {
							html += " class=\"holiday\"";
							holiday = true;
						}
					}
					if (!holiday) {
						if (index2 == 0) {
							html += " class=\"sunday\"";
						} else if (index2 == 6) {
							html += " class=\"saturday\"";
						}
					}
				}
				if (Object.keys(hashmapHoliday).length > 0) {
					// 공휴일(임시 공휴일 포함)
					var holidayInfo = hashmapHoliday[calendarYear + "-" + calendarMonth + "-" + calendarDay];
					if (holidayInfo == undefined || holidayInfo == null) {
						holidayInfo = hashmapHoliday[calendarMonth + "-" + calendarDay];
					}
					if (holidayInfo != undefined && holidayInfo != null) {
						html += " title=\"" + holidayInfo.title + "\"";
					}
				}
				html += ">" + calendarDay + "</span>";
			} else {
				html += ">";
			}
			html += "</td>";
			calendarPos++;
		}
		html += "</tr>";
	}
	html += "</tbody>";
	html += "</table>";
	return html;
}

 

이전 달 / 다음 달 이동 버튼 클릭 이벤트 처리 시 달력의 연도와 월을 별도로 가져오는 문제점을 해결하기 위해 id가 calendar인 <div> 태그의 data-date 속성에 연월일(yyyy-MM-dd)을 추가합니다. 함수 명도 calendar에서 calendarMonth으로 변경합니다.

function calendarMonth(date) {
	// 년월
	$(".calendar-yearmonth").html(date.getFullYear() + "." + (date.getMonth() + 1));
	
	// 해시맵(HashMap)
	var hashmapHoliday = [];
	hashmapHoliday["2022-3-9"] = {"title" : "20대 대통령 선거일"};
	
	var options = {
		showDay : true,
		showFullDayName : true,
		showToday : true,
		arHoliday : hashmapHoliday
	};
	
	var html = calendarHTML(date, options);
	$("#calendar").attr("data-date", date.getFullYear() + "-" + (date.getMonth() + 1));
	$("#calendar").html(html);
}

calendarMonth(new Date());

 

 

이전 달 / 다음 달 이동 버튼 클릭 이벤트에서 id가 calendar인 <div> 태그의 data-date 속성을 읽어오게 수정합니다. 그리고 모든 이동 버튼 클릭 이벤트에서 함수명을 calendar에서 calendarMonth으로 변경합니다.

// 이전 달 이동 버튼
$(".calendar-controls > .calendar-prev").on("click", function() {
	var yearmonth = $("#calendar").attr("data-date").split("-");
	calendarMonth(new Date(parseInt(yearmonth[0]), parseInt(yearmonth[1]) - 2, 1));
});

// 다음 달 이동 버튼
$(".calendar-controls > .calendar-next").on("click", function() {
	var yearmonth = $("#calendar").attr("data-date").split("-");
	calendarMonth(new Date(parseInt(yearmonth[0]), parseInt(yearmonth[1]), 1));
});

// 오늘 이동 버튼
$(".calendar-controls > .calendar-today").on("click", function() {
	calendarMonth(new Date());
});

 

전체 소스입니다.

<!DOCTYPE html>
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<title>달력 만들기</title>
		<style>
			.calendar { width: 500px; }
			.calendar table { width: 100%; }
			.calendar table > caption { font-size: larger; font-weight: bolder; color: red; text-align: left; padding: 10px 20px; }
			.calendar table > thead > tr > th.saturday { color: gray; }
			.calendar table > thead > tr > th.sunday { color: red; }
			.calendar table > tbody > tr > td { padding: 10px 10px; text-align: center; }
			.calendar table > tbody > tr > td > span { display: block; padding: 10px 10px; }
			.calendar table > tbody > tr > td > span.today { border: 1px solid red; border-radius: 50%; color: white; background-color: red; padding: 9px 9px; }
			.calendar table > tbody > tr > td > span.saturday { color: gray; }
			.calendar table > tbody > tr > td > span.sunday { color: red; }
			.calendar table > tbody > tr > td > span.holiday { color: red; }
			.calendar > .calendar-header { position: relative; height: 50px; margin: 0px 20px; }
			.calendar > .calendar-header > .calendar-yearmonth { position: relative; font-size: larger; font-weight: bolder; color: red; line-height: 50px; }
			.calendar > .calendar-header > .calendar-controls { position: absolute; top: 11px; right: 0px; }
			.calendar > .calendar-header > .calendar-controls > button { float: left; width: 25px; height: 28px; padding: 1px 4px 0 4px; border: 1px solid #cbcbcb; background-color: #fff; font-size: 12px; cursor: pointer; }
			.calendar > .calendar-header > .calendar-controls > button.calendar-today { width: 40px; }
		</style>
		<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
		<script src="calendar.js"></script>
	</head>
	<body>
		<div class="calendar">
			<div class="calendar-header">
				<span class="calendar-yearmonth"></span>
				<div class="calendar-controls">
					<button type="button" class="calendar-prev">&lt;</button>
					<button type="button" class="calendar-next">&gt;</button>
					<button type="button" class="calendar-today">오늘</button>
				</div>
			</div>
			<div id="calendar"></div>
		</div>
		<script>
			// 이전 달 이동 버튼
			$(".calendar-controls > .calendar-prev").on("click", function() {
				//var yearmonth = $(".calendar-yearmonth").text().split(".");
				var yearmonth = $("#calendar").attr("data-date").split("-");
				calendarMonth(new Date(parseInt(yearmonth[0]), parseInt(yearmonth[1]) - 2, 1));
			});
			
			// 다음 달 이동 버튼
			$(".calendar-controls > .calendar-next").on("click", function() {
				//var yearmonth = $(".calendar-yearmonth").text().split(".");
				var yearmonth = $("#calendar").attr("data-date").split("-");
				calendarMonth(new Date(parseInt(yearmonth[0]), parseInt(yearmonth[1]), 1));
			});
			
			// 오늘 이동 버튼
			$(".calendar-controls > .calendar-today").on("click", function() {
				calendarMonth(new Date());
			});
			
			// 날짜 클릭
			$(document).on("click", ".calendar table > tbody > tr > td", function(event) {
				//event.preventDefault();
				event.stopPropagation();
				var eventTarget = event.target;
				while (eventTarget.tagName != "TD") {
					eventTarget = eventTarget.parentElement;
				}
				if ($(eventTarget).attr("data-date") != undefined) {
					alert($(eventTarget).attr("data-date"));
				}
			});
			
			function calendarMonth(date) {
				// 년월
				$(".calendar-yearmonth").html(date.getFullYear() + "." + (date.getMonth() + 1));
				
				// 해시맵(HashMap)
				var hashmapHoliday = [];
				hashmapHoliday["2022-3-9"] = {"title" : "20대 대통령 선거일"};
				
				var options = {
					showDay : true,
					showFullDayName : true,
					showToday : true,
					arHoliday : hashmapHoliday
				};
				
				var html = calendarHTML(date, options);
				$("#calendar").attr("data-date", date.getFullYear() + "-" + (date.getMonth() + 1));
				$("#calendar").html(html);
			}
			
			calendarMonth(new Date());
		</script>
	</body>
</html>

 

728x90
반응형