티스토리 뷰
JavaScript로 동적 재사용 가능한 Todo 달력 라이브러리 만들기: 클래스 기반 모듈화와 동적 ID 관리
Coding Life 2024. 12. 24. 20:04아래의 코드는 todolist.js 파일이다.
이 파일을 여러 웹사이트에서 사용하는 모듈화를 하고 싶다. 어떤 아이디어가 있을까?
이 js파일은 달력을 구현해서 스케줄을 달력에 표시해서 클릭하면 해당 발주서나 관련 파일을 보여주고, 휴일을 표시하고, 스케줄을 입력하는 등의 역할을 하는 코드이다.
이 코드를 라이브러리나 모듈화해서 어떤 웹사이트에서든 불러서 사용하는 형태를 만들고 싶다.
var ajaxRequest5 = null;
$(document).ready(function() {
let todo_currentMonth = new Date().getMonth();
let todo_currentYear = new Date().getFullYear();
function todo_fetchCalendarData(month, year, search = null) {
showWaitingModal();
// ajax 요청 중복호출 금지
if (ajaxRequest5 !== null ) {
ajaxRequest5.abort();
hideSavingModal();
}
ajaxRequest5 = $.ajax({
// $.ajax({
url: "/todo/fetch_todo.php",
type: "post",
data: { month: month + 1, year: year , search : search },
dataType: "json",
success: function(response) {
console.log(response);
if (response.work_data) {
const classifiedData = classifyProcesses(response);
// updateProcessUI(classifiedData);
}
// 필터에 따라 데이터 표시
let selectedFilter = $('input[name="filter"]:checked').attr('id');
let filteredData = filterDataByProcess(response.work_data, selectedFilter);
// console.log('filteredData : ', filteredData);
let calendarHtml = todo_generateCalendarHtml(
response.todo_data,
response.leave_data,
selectedFilter,
response.holiday_data,
filteredData,
response.as_data
);
$('#todo-calendar-container').html(calendarHtml);
var showTodoView = getCookie("showTodoView");
var todoCalendarContainer = $("#todo-board");
if (showTodoView === "show") {
todoCalendarContainer.css("display", "block");
} else {
todoCalendarContainer.css("display", "none");
}
ajaxRequest5 = null;
hideSavingModal();
},
error: function() {
console.log('fetch_todo 가져오는데 실패!!!!');
ajaxRequest5 = null;
}
});
}
function classifyProcesses(data) {
const processCategories = {};
data.work_data.forEach(workItem => {
const order_process = workItehttp://m.order_process.split(','); // order_process를 쉼표로 분리
order_process.forEach(process => {
if (!processCategories[process]) {
processCategories[process] = [];
}
processCategories[process].push({
num: workItem.num,
order_TIME_DATE: workItem.order_TIME_DATE,
order_CUST_DES: workItem.order_CUST_DES,
order_Title: workItem.order_Title,
order_state: workItem.order_state
});
});
});
return processCategories;
}
function filterDataByProcess(data, selectedFilter) {
if (selectedFilter === "filter_all") {
return data; // 전체 데이터 반환
}
const processMapping = {
filter_l: "레이져",
filter_bs: "절곡 및 서브",
filter_w: "용접",
filter_s: "쇼트",
filter_p: "도장",
filter_t: "운송"
};
const selectedProcess = processMapping[selectedFilter];
if (!selectedProcess) return [];
return data.filter(workItem => {
const order_process = workItehttp://m.order_process.split(','); // order_process를 쉼표로 분리
return order_process.includes(selectedProcess); // 선택된 공정이 order_process에 포함되어 있는지 확인
});
}
// 공정별 데이터를 UI에 업데이트
function updateProcessUI(classifiedData) {
Object.keys(classifiedData).forEach(process => {
console.log(`=== ${process} ===`);
classifiedData[process].forEach(item => {
console.log(`${item.order_TIME_DATE} | ${item.order_CUST_DES} | ${item.order_Title}`);
});
});
}
// 초기 데이터 로드
todo_fetchCalendarData(todo_currentMonth, todo_currentYear);
function todo_generateCalendarHtml(todoData, leaveData, selectedFilter, holidayData, workData, asData) {
// console.log('todoData ', todoData);
// console.log('selectedFilter ', selectedFilter);
const daysOfWeek = ['일', '월', '화', '수', '목', '금', '토'];
let date = new Date(todo_currentYear, todo_currentMonth, 1);
let firstDay = date.getDay();
let lastDate = new Date(todo_currentYear, todo_currentMonth + 1, 0).getDate();
let today = new Date();
let todayYear = today.getFullYear();
let todayMonth = today.getMonth();
let todayDate = today.getDate();
let calendarHtml = '<div class="row d-flex"> ';
calendarHtml += '<div class="d-flex justify-content-center">';
calendarHtml += '<table id="todo-list" class="table" style="width: 100%; table-layout: fixed;">';
calendarHtml += '<thead class="table-info text-start"><tr>';
daysOfWeek.forEach((day, index) => {
// '일'과 '토'는 5%씩, 나머지는 18%씩 비율 설정
let width = (day === '일' || day === '토') ? '5%' : '18%';
// '일'과 '토'는 'text-danger' 클래스 추가
let textClass = (day === '일' || day === '토') ? 'text-danger' : '';
calendarHtml += `<th class="fs-6 text-start ${textClass}" style="width: ${width};">${day}</th>`;
});
calendarHtml += '</tr></thead><tbody class="text-start">';
let day = 1;
for (let i = 0; i < 6; i++) {
calendarHtml += '<tr>';
for (let j = 0; j < 7; j++) {
if (i === 0 && j < firstDay) {
calendarHtml += '<td></td>';
} else if (day > lastDate) {
calendarHtml += '<td></td>';
} else {
let currentDate = new Date(todo_currentYear, todo_currentMonth, day);
currentDate.setHours(0, 0, 0, 0); // 시간을 0으로 설정하여 날짜만 비교
let dayClass = (j === 0 || j === 6) ? 'red-day' : '';
// 오늘 날짜인지 확인
if (currentDate.getFullYear() === todayYear && currentDate.getMonth() === todayMonth && currentDate.getDate() === todayDate) {
dayClass += ' today-bg';
}
// 한국 시간대를 고려한 날짜 변환 함수
function convertToKST(dateString) {
const utcDate = new Date(dateString + 'T00:00:00Z'); // UTC로 변환
const kstDate = new Date(utcDate.getTime() + 9 * 60 * 60 * 1000); // 9시간 추가 (KST)
kstDate.setHours(0, 0, 0, 0); // 시간을 0으로 설정
return kstDate;
}
// holidayData에 해당 날짜가 있는지 확인
let holidayForDay = holidayData.filter(item => {
let startDate = convertToKST(item.startdate);
let endDate = item.enddate && item.enddate !== '0000-00-00' ? convertToKST(item.enddate) : startDate;
return currentDate >= startDate && currentDate <= endDate;
});
// 만약 해당 날짜가 holiday에 포함되면 red-day 추가
if (holidayForDay.length > 0) {
dayClass += ' text-danger';
}
calendarHtml += `<td class="${dayClass}"><div class="fw-bold fs-6">${day} <button type="button" class="event btn btn-outline-dark btn-sm" style="border:0px;" data-date="${todo_currentYear}-${('0' + (todo_currentMonth + 1)).slice(-2)}-${('0' + day).slice(-2)}" > <i class="bi bi-plus"></i> </button> </div>`;
// holiday 표시
holidayForDay.forEach(item => {
calendarHtml += `<div><span class='badge bg-danger'>${item.comment}</span></div>`;
});
// 주말 제외
if (j !== 0 && j !== 6) {
let leaveDataForDay = leaveData.filter(item => {
let leaveStart = new Date(item.al_askdatefrom);
let leaveEnd = new Date(item.al_askdateto);
leaveStart.setHours(0, 0, 0, 0);
leaveEnd.setHours(0, 0, 0, 0);
currentDate.setHours(0, 0, 0, 0);
return currentDate >= leaveStart && currentDate <= leaveEnd;
});
leaveDataForDay.forEach(item => {
calendarHtml += '<div class="leave-info"><span class="fw-bold text-dark">' + item.author + ' (' + item.al_item + ':' + item.al_content + ')</span></div>';
});
}
// 플러스 버튼 눌러서 표현 todo_data 표시
let dayData = todoData.filter(item => {
let orderDate = new Date(item.orderdate);
orderDate.setHours(0, 0, 0, 0); // 시간을 0으로 설정
return orderDate.getDate() === day && orderDate.getMonth() === todo_currentMonth && orderDate.getFullYear() === todo_currentYear;
});
dayData.forEach(item => {
if(item.towhom === '') item.towhom = '';
else item.towhom = item.towhom;
switch (item.work_status) {
case '작성':
item.work_status = '';
break;
case '완료':
item.work_status = '<span class="badge bg-danger">' + item.work_status + '</span>';
break;
}
calendarHtml += '<div class="todo-event event" data-id="' + item.num + '"><span class="badge bg-primary">' + item.towhom + '</span> <span class="badge bg-dark">' + item.title + item.work_status + '</span> </div>';
});
// work_data (납기일 기준) 표시
let work_dayData = workData.filter(item => {
let calDate = new Date(item.order_TIME_DATE);
calDate.setHours(0, 0, 0, 0); // 시간을 0으로 설정
return calDate.getDate() === day && calDate.getMonth() === todo_currentMonth && calDate.getFullYear() === todo_currentYear;
});
work_dayData.forEach(item => {
// 문자열이 10자 이상일 경우 '...' 추가하는 함수
function truncateText(text, maxLength = 6) {
if (text.length > maxLength) {
return text.substring(0, maxLength) + '...';
}
return text;
}
// 제목을 10자까지만 표시하고, 넘으면 '...' 추가
if(item.order_state == 'done')
calendarHtml += `<div class="todo-myorder " data-id="${item.num}"><span class="bg-info-subtle border border-info-subtle rounded-pill me-1" style="cursor:pointer;" >${item.order_CUST_DES}</span> <span class="text-secondary" style="cursor:pointer;" >${item.order_Title}</span> </div>`;
else
calendarHtml += `<div class="todo-myorder " data-id="${item.num}"><span class="bg-info-subtle border border-info-subtle rounded-pill me-1" style="cursor:pointer;" >${item.order_CUST_DES}</span> <span class="text-danger fw-bold" style="cursor:pointer;" >${item.order_Title}</span> </div>`;
});
calendarHtml += '</td>';
day++;
}
}
calendarHtml += '</tr>';
}
calendarHtml += '</tbody></table></div></div>';
let startDate = new Date(todo_currentYear, todo_currentMonth, 1);
let endDate = new Date(todo_currentYear, todo_currentMonth, lastDate);
$('#todo-current-period').text(todo_currentYear + '/' + ('0' + (todo_currentMonth + 1)).slice(-2));
return calendarHtml;
}
$('#todo-calendar-container').on('click', '.event', function() {
let num = $(this).data('id');
let date = $(this).data('date');
loadForm(num, date);
});
// 작업지시서 클릭하면 화면에 띄우기
$('#todo-calendar-container').on('click', '.todo-myorder', function() {
let num = $(this).data('id');
viewOrder(num, 'close');
});
function loadForm(num, date) {
let todo_mode = num == 'undefined' || num == null ? 'insert' : 'update';
// console.log(date);
// console.log(num);
// console.log(mode);
$("#todo_mode").val(todo_mode);
$("#num").val(num);
$.ajax({
type: "POST",
url: "/todo/fetch_modal.php",
data: { todo_mode: todo_mode, todo_num: num, seldate : date },
dataType: "html",
success: function(response){
document.querySelector(".todo_modal-body .todo_custom-card").innerHTML = response;
$("#todoModal").show();
$(".todo-close").on("click", function() {
$("#todoModal").hide();
});
$("#todo_closeBtn").on("click", function() {
$("#todoModal").hide();
});
// Log 파일보기
$("#showlogBtn").click( function() {
var num = $("#num").val();
// table 이름을 넣어야 함
var workitem = 'todos' ;
// 버튼 비활성화
var btn = $(this);
popupCenter("/Showlog.php?num=" + num + "&workitem=" + workitem , '로그기록 보기', 500, 500);
btn.prop('disabled', false);
});
// 요청처리일을 입력하면 진행상태를 '완료'로 변경하고, 날짜를 지우면 '작성'으로 변경
$('#deadline').change(function() {
if ($(this).val()) {
$('#work_status').val('완료');
} else {
$('#work_status').val('작성');
}
});
// 저장 버튼
$("#todo_saveBtn").on("click", function() {
// 각 요소의 값을 수집
var formData = {
tablename: "todos", // 예시로 고정된 값 설정
todo_mode: $("#todo_mode").val() || "insert",
todo_num: $("#todo_num").val() || null, // num 값
todo_orderdate: $("#todo_orderdate").val(),
todo_first_writer: $("#todo_first_writer").val(),
todo_towhom: $(".approver-checkbox:checked").map(function() {
return $(this).data("user-name");
}).get().join(", "), // 선택된 사용자 목록을 문자열로 병합
todo_reply: $("#todo_reply").val(),
todo_deadline: $("#todo_deadline").val(),
todo_work_status: $("#todo_work_status").val(),
todo_title: $("#todo_title").val()
};
// AJAX 요청
$.ajax({
url: "/todo/process.php",
type: "post",
data: formData,
success: function(response) {
console.log(response);
Toastify({
text: "저장완료",
duration: 3000,
close: true,
gravity: "top",
position: "center",
backgroundColor: "#4fbe87",
}).showToast();
$("#todoModal").hide();
todo_fetchCalendarData(todo_currentMonth, todo_currentYear); // 변경된 데이터만 다시 로드
},
error: function(jqxhr, status, error) {
console.log(jqxhr, status, error);
}
});
});
// 삭제 버튼
$("#todo_deleteBtn").on("click", function() {
var user_name = $("#todo_user_name").val(); // 사용자 이름
var first_writer = $("#todo_first_writer").val(); // 작성자 이름
var admin = $("#admin").val();
console.log('admin : ', admin);
console.log('todo_num : ', $("#todo_num").val());
if (user_name !== first_writer && admin !== '1') {
Swal.fire({
title: '삭제불가',
text: "작성자와 관리자만 삭제 가능합니다.",
icon: 'error',
confirmButtonText: '확인'
});
return;
}
Swal.fire({
title: '자료 삭제',
text: "삭제는 신중! 정말 삭제하시겠습니까?",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '삭제',
cancelButtonText: '취소'
}).then((result) => {
if (result.isConfirmed) {
// 필요한 데이터만 수집
var formData = {
todo_mode: "delete",
todo_num: $("#todo_num").val(), // 삭제할 데이터의 고유 ID
tablename: "todos" // 예시로 고정된 테이블 이름
};
$.ajax({
url: "/todo/process.php",
type: "post",
data: formData,
success: function(response) {
Toastify({
text: "파일 삭제완료",
duration: 2000,
close: true,
gravity: "top",
position: "center",
style: {
background: "linear-gradient(to right, #00b09b, #96c93d)"
},
}).showToast();
$("#todoModal").hide();
todo_fetchCalendarData(todo_currentMonth, todo_currentYear); // 변경된 데이터만 다시 로드
},
error: function(jqxhr, status, error) {
console.log(jqxhr, status, error);
}
});
}
});
});
// 체크박스 클릭시 처리
function updateApproversInput() {
let approvers = [];
$('.approver-checkbox:checked').each(function() {
approvers.push($(this).data('user-name'));
});
$('#towhom').val(approvers.join(', '));
}
$('.approver-checkbox').change(function() {
updateApproversInput();
});
// 기존에 선택된 사용자를 반영합니다.
if($('#towhom').val()) {
let selectedApprovers = $('#towhom').val().split(', ');
$('.approver-checkbox').each(function() {
if (selectedApprovers.includes($(this).data('user-name'))) {
$(this).prop('checked', true);
}
});
}
},
error: function(jqxhr, status, error) {
console.log("AJAX Error: ", status, error);
}
});
}
// 초기 라디오 버튼 상태 설정 및 필터 변경 이벤트
function initializeRadioButtons() {
let selectedFilter = getCookie("todoFilter") || 'filter_all';
$('#' + selectedFilter).prop('checked', true);
todo_fetchCalendarData(todo_currentMonth, todo_currentYear);
}
// 라디오 버튼 변경 이벤트 핸들러
$('input[name="filter"]').on('change', function () {
let selectedFilter = $('input[name="filter"]:checked').attr('id');
setCookie("todoFilter", selectedFilter, 10);
var search = $('#searchTodo').val();
// console.log(search);
todo_fetchCalendarData(todo_currentMonth, todo_currentYear, search);
});
// 일정검색시
$('#searchTodoBtn').off('click').on('click', function () {
var search = $('#searchTodo').val();
// console.log(search);
todo_fetchCalendarData(todo_currentMonth, todo_currentYear, search);
});
$('#todo-prev-month').click(function() {
var search = $('#searchTodo').val();
todo_currentMonth--;
if (todo_currentMonth < 0) {
todo_currentMonth = 11;
todo_currentYear--;
}
todo_fetchCalendarData(todo_currentMonth, todo_currentYear, search);
});
$('#todo-next-month').click(function() {
var search = $('#searchTodo').val();
todo_currentMonth++;
if (todo_currentMonth > 11) {
todo_currentMonth = 0;
todo_currentYear++;
}
todo_fetchCalendarData(todo_currentMonth, todo_currentYear, search);
});
$('#todo-current-month').click(function() {
var search = $('#searchTodo').val();
todo_currentMonth = new Date().getMonth();
todo_currentYear = new Date().getFullYear();
todo_fetchCalendarData(todo_currentMonth, todo_currentYear, search);
});
/* 부트스트랩 툴팁 */
// var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
// var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
// return new bootstrap.Tooltip(tooltipTriggerEl);
// });
// 페이지 로드 시 초기화
initializeRadioButtons();
});
todoList.js 파일을 모듈화하고 재사용 가능한 형태로 변환하려면 아래와 같은 단계와 설계를 따르는 것이 좋습니다. 이 작업은 다른 웹사이트에서도 쉽게 사용할 수 있는 라이브러리로 만드는 데 초점이 맞춰져 있습니다.
1. 파일 구조 설계
라이브러리를 모듈화하기 위해 파일 구조를 다음과 같이 정리합니다:
/todo-library
├── index.js # 핵심 라이브러리 파일 (기능 제공)
├── styles.css # 기본 스타일 정의
├── README.md # 사용법 문서
├── examples/ # 샘플 구현 코드
└── dist/ # 빌드된 파일 배포 폴더
2. 모듈화 작업
a. 전역 변수 제거 및 클래스 기반 구현
코드를 클래스 또는 ES6 모듈 형태로 변경하여 각 인스턴스가 독립적으로 동작하도록 합니다.
코드 예시:
class TodoCalendar {
constructor(container, options = {}) {
this.container = container; // HTML 컨테이너 요소
this.options = options;
this.currentMonth = new Date().getMonth();
this.currentYear = new Date().getFullYear();
this.ajaxRequest = null;
// 초기화 작업
this.init();
}
init() {
// HTML 구조 생성
this.renderCalendar();
// 초기 데이터 로드
this.fetchCalendarData();
// 이벤트 바인딩
this.bindEvents();
}
renderCalendar() {
const calendarHtml = `
<div id="todo-calendar-container">
<div id="todo-controls">
<button id="todo-prev-month">Prev</button>
<button id="todo-next-month">Next</button>
<button id="todo-current-month">Today</button>
</div>
<div id="todo-calendar"></div>
</div>`;
this.container.innerHTML = calendarHtml;
}
fetchCalendarData(search = null) {
// 기존 요청 취소
if (this.ajaxRequest) this.ajaxRequest.abort();
this.ajaxRequest = $.ajax({
url: this.options.apiUrl || "/todo/fetch_todo.php",
type: "POST",
data: {
month: this.currentMonth + 1,
year: this.currentYear,
search
},
dataType: "json",
success: (response) => {
this.renderCalendarData(response);
this.ajaxRequest = null;
},
error: (error) => {
console.error("Failed to fetch calendar data:", error);
this.ajaxRequest = null;
}
});
}
renderCalendarData(data) {
// 캘린더 데이터로 UI 갱신
const calendarContainer = this.container.querySelector("#todo-calendar");
calendarContainer.innerHTML = this.generateCalendarHtml(data);
}
generateCalendarHtml(data) {
const daysOfWeek = ['일', '월', '화', '수', '목', '금', '토'];
let html = '<table class="todo-calendar-table"><thead><tr>';
daysOfWeek.forEach(day => {
html += `<th>${day}</th>`;
});
html += '</tr></thead><tbody>';
// 날짜 생성 로직
const firstDay = new Date(this.currentYear, this.currentMonth, 1).getDay();
const lastDate = new Date(this.currentYear, this.currentMonth + 1, 0).getDate();
let day = 1;
for (let i = 0; i < 6; i++) {
html += '<tr>';
for (let j = 0; j < 7; j++) {
if (i === 0 && j < firstDay) {
html += '<td></td>';
} else if (day > lastDate) {
html += '<td></td>';
} else {
html += `<td>${day}</td>`;
day++;
}
}
html += '</tr>';
}
html += '</tbody></table>';
return html;
}
bindEvents() {
// 버튼 이벤트 핸들링
this.container.querySelector("#todo-prev-month").addEventListener("click", () => {
this.currentMonth--;
if (this.currentMonth < 0) {
this.currentMonth = 11;
this.currentYear--;
}
this.fetchCalendarData();
});
this.container.querySelector("#todo-next-month").addEventListener("click", () => {
this.currentMonth++;
if (this.currentMonth > 11) {
this.currentMonth = 0;
this.currentYear++;
}
this.fetchCalendarData();
});
this.container.querySelector("#todo-current-month").addEventListener("click", () => {
this.currentMonth = new Date().getMonth();
this.currentYear = new Date().getFullYear();
this.fetchCalendarData();
});
}
}
// 사용 예시
document.addEventListener("DOMContentLoaded", () => {
const container = document.getElementById("calendar");
const todoCalendar = new TodoCalendar(container, { apiUrl: "/todo/fetch_todo.php" });
});
b. CSS 스타일 분리
styles.css에 기본 스타일을 분리하고, 프로젝트에 필요한 경우 덮어쓸 수 있도록 만듭니다.
.todo-calendar-table {
width: 100%;
border-collapse: collapse;
}
.todo-calendar-table th, .todo-calendar-table td {
border: 1px solid #ddd;
text-align: center;
padding: 8px;
}
3. 배포 및 패키지화
- NPM 배포: package.json을 설정하여 NPM 패키지로 등록하면 다른 프로젝트에서 쉽게 설치 가능.
- CDN 제공: 빌드된 파일을 CDN에 업로드해 <script> 태그를 통해 불러오기.
4. 사용법 문서화
README.md에 사용법을 문서화하여 모든 설정 옵션과 이벤트 바인딩 방법을 설명합니다.
5. 장점
- 재사용성 증가: 다양한 웹사이트에서 같은 코드 사용.
- 유지보수 용이: 코드가 한 곳에서 관리됨.
- 커스터마이징 가능: 옵션을 통해 기능 확장.
위의 코드에서 개선해야할 것이 있다. todolist의 div 요소 id를 전달하고, fetch_todo.php 파일 이름도 변수로 전달해서 어떤것이든 적용하도록 한다. 이와 같이 어떤 값을 넣어서 해당 요소의 규칙으로 div요소들의 이름을 만드는 것이다. 예를 들어 좌측이동 아이콘의 클릭은 해당 div 전달한 요소의 이름에 leftkey 오른쪽이동은 rightkey 이런식으로 이름을 부여하는 것이다. 그러면 div요소의 이름을 전달하면 해당요소에서 id를 연동해서 변겨되는 코드가 되면 재활용성이 좋아진다.
코드를 개선하여 전달받은 div 요소 ID와 fetch_todo.php와 같은 변수를 활용해 동적으로 이름을 생성하고 이를 기반으로 작업을 수행하도록 수정하겠습니다.
개선된 코드
class TodoCalendar {
constructor(containerId, options = {}) {
this.containerId = containerId; // HTML 컨테이너 ID
this.container = document.getElementById(containerId); // 실제 DOM 요소
this.options = options;
this.currentMonth = new Date().getMonth();
this.currentYear = new Date().getFullYear();
this.ajaxRequest = null;
// 설정된 이름 규칙
this.leftKeyId = `${containerId}-leftkey`;
this.rightKeyId = `${containerId}-rightkey`;
this.currentKeyId = `${containerId}-currentkey`;
// 초기화
this.init();
}
init() {
// HTML 구조 생성
this.renderCalendar();
// 초기 데이터 로드
this.fetchCalendarData();
// 이벤트 바인딩
this.bindEvents();
}
renderCalendar() {
const calendarHtml = `
<div id="${this.containerId}-controls">
<button id="${this.leftKeyId}" class="btn btn-primary">Prev</button>
<button id="${this.rightKeyId}" class="btn btn-primary">Next</button>
<button id="${this.currentKeyId}" class="btn btn-primary">Today</button>
</div>
<div id="${this.containerId}-calendar"></div>
`;
this.container.innerHTML = calendarHtml;
}
fetchCalendarData(search = null) {
if (this.ajaxRequest) this.ajaxRequest.abort(); // 중복 요청 취소
const fetchUrl = this.options.apiUrl || "/todo/fetch_todo.php";
this.ajaxRequest = $.ajax({
url: fetchUrl,
type: "POST",
data: {
month: this.currentMonth + 1,
year: this.currentYear,
search
},
dataType: "json",
success: (response) => {
this.renderCalendarData(response);
this.ajaxRequest = null;
},
error: (error) => {
console.error("Failed to fetch calendar data:", error);
this.ajaxRequest = null;
}
});
}
renderCalendarData(data) {
const calendarContainer = document.getElementById(`${this.containerId}-calendar`);
calendarContainer.innerHTML = this.generateCalendarHtml(data);
}
generateCalendarHtml(data) {
const daysOfWeek = ['일', '월', '화', '수', '목', '금', '토'];
let html = '<table class="todo-calendar-table"><thead><tr>';
daysOfWeek.forEach(day => {
html += `<th>${day}</th>`;
});
html += '</tr></thead><tbody>';
const firstDay = new Date(this.currentYear, this.currentMonth, 1).getDay();
const lastDate = new Date(this.currentYear, this.currentMonth + 1, 0).getDate();
let day = 1;
for (let i = 0; i < 6; i++) {
html += '<tr>';
for (let j = 0; j < 7; j++) {
if (i === 0 && j < firstDay) {
html += '<td></td>';
} else if (day > lastDate) {
html += '<td></td>';
} else {
html += `<td>${day}</td>`;
day++;
}
}
html += '</tr>';
}
html += '</tbody></table>';
return html;
}
bindEvents() {
document.getElementById(this.leftKeyId).addEventListener("click", () => {
this.currentMonth--;
if (this.currentMonth < 0) {
this.currentMonth = 11;
this.currentYear--;
}
this.fetchCalendarData();
});
document.getElementById(this.rightKeyId).addEventListener("click", () => {
this.currentMonth++;
if (this.currentMonth > 11) {
this.currentMonth = 0;
this.currentYear++;
}
this.fetchCalendarData();
});
document.getElementById(this.currentKeyId).addEventListener("click", () => {
this.currentMonth = new Date().getMonth();
this.currentYear = new Date().getFullYear();
this.fetchCalendarData();
});
}
}
// 사용법
document.addEventListener("DOMContentLoaded", () => {
const todoCalendar = new TodoCalendar("my-todo-calendar", {
apiUrl: "/todo/fetch_todo.php"
});
const anotherCalendar = new TodoCalendar("another-calendar", {
apiUrl: "/other-api/fetch_todo.php"
});
});
주요 개선점
- 동적 ID 생성 및 전달:
- containerId를 기반으로 컨트롤 요소(leftKey, rightKey, currentKey)의 ID를 자동 생성.
- 특정 ID가 고정되지 않고, 전달된 이름에 따라 다른 컨트롤을 생성.
- API URL 동적 전달:
- apiUrl 옵션으로 fetch_todo.php와 같은 API를 동적으로 변경 가능.
- 캘린더 독립성:
- 여러 캘린더를 한 페이지에서 독립적으로 생성하고 동작하도록 수정.
- 재사용성 증가:
- 어떤 DOM 요소 ID를 전달하든 자동으로 연동되므로 코드 수정 없이 다양한 환경에 대응 가능.
사용 예시
<div id="my-todo-calendar"></div>
<div id="another-calendar"></div>
위의 예시처럼 다른 ID를 가진 요소에 달력을 생성하고 각기 다른 API를 설정할 수 있습니다. 페이지에서 여러 달력을 동시에 사용할 때도 충돌 없이 동작합니다.
'IT tech Coding > javascript' 카테고리의 다른 글
[자바스크립트] 입력 필드에서 숫자를 3자리마다 콤마로 포맷하고 소수점을 유지하는 완벽한 방법 (0) | 2025.01.08 |
---|---|
JavaScript로 간단한 파일 업로드와 관리 시스템 만들기 (0) | 2025.01.04 |
[기업ERP] 로트번호 입력과 성적서 보기 기능 구현하기 (0) | 2024.12.17 |
자바스크립트로 문자열을 날짜 형식으로 변환하는 specialFormatDate 함수 만들기 (0) | 2024.12.10 |
Isotope.js: 쉽고 멋진 필터링과 정렬 효과를 구현해보세요! (0) | 2024.12.08 |
- Total
- Today
- Yesterday
- #파이썬패키징
- #InstallForge
- 오토핫키가이드
- 파이썬코드줄바꿈방법
- #NSIS
- #프로그램설치
- 엑셀입력보호
- 코딩튜토리얼
- 테크에능한여성
- Bootstrap 5
- #InnoSetup
- General error: 2031
- 오블완
- 코딩효율성
- 효율적코딩방법
- 프로그래머생활
- coalesce는 한국어로 "코얼레스크" 또는 "코얼리스"
- 구글드라이브API
- 엑셀보호
- 스크립트작성기초
- json파일편하게보는법
- 1. #웹개발 2. #로트번호 3. #성적서보기 4. #ajax 5. #jquery 6. #php 7. #프론트엔드 8. #백엔드 9. #부트스트랩 10. #웹기능구현
- 티스토리챌린지
- 엑셀셀보호
- sql문장 날짜계산
- json파일형태보기
- 뫄프로그래밍
- chatGPT3.5파이썬버전
- isset을 적용해야 하는 이유
- ajax오류메시지
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |