달력 생성 함수를 공용으로 사용하기 위해 웹 페이지에서 정보를 가져와 사용하는 문제점을 해결하고 확장해서 사용하기 위해서입니다.
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"><</button>
<button type="button" class="calendar-next">></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>