본문 바로가기

프로젝트 개발기/Status 200 ( Team Project )

5. TEAM 프로젝트: 캘린더 기능 구현 - 모양 표시


상세한 개발 내역을 작성할려고 했으나, 여러 번의 디버그가 있었고 개발 단계에서 변경되는 점 또한 많았다. 

때문에 완성된 캘린더를 기능 별로 정리하고, 개선점을 작성하려고 한다. 

(TimeLine에 작성해 봤으나 글이 한눈에 들어오지 않는다는 단점이 있어서 방법을 바꿨다.)

 


1. 캘린더 모양 표시를 위한 기반 함수 

캘린더의 모든 모양과 기능은 Java Script (Ecma Script)를 기반으로 한다. 

마크업을 직접 진행하고 이를 조작하면 개발 초기에는 편할 수 있으나, 실제로 데이터를 표현하고 수정할 때 제약 조건이 발생할 수 있다. 

연 단위 스케줄은 적어도 1000~2000개 사이의 엘레먼트의 조작이 필요한데, 이를 마크업으로 조작하여도 결국 Node 단위로 데이터를 수정해야 년도별 데이터를 정확하게 표현할 수 있다. 

 

<div id="calendar"></div>

 

때문에, 마크업은 Body에 id가 Calendar인 div 태그 하나를 지정하고 모든 단위를 Node로 조작하였다. 

 

// 유저키를 세션에서 가져옴
String userKey = null;

try{
    userKey = "'"+session.getAttribute("loginUserId").toString()+"'"; 
}
catch(Exception e){
    System.out.println("Session get Error: calendarMain.jsp: line 10: >>" +e);
}

userKey는 모든 데이터를 식별하는데 사용한다. 스크립트 릿으로 세션에서 userKey를 가져온다.

 

 

1) 기본 데이터 모양

let BasicDay = {
    year: 2020, 
    month: 1, 
    day: 1,
    hour: 00, 
    minites: 00
};

Date로 조작하는 모든 데이터는 위와 같은 모양을 기반으로 한다.

Map 형태의 자료구조로 Key에 따른 접근이 쉽게 설정하고 모든 함수를 구현하였다.

 

2) 월 및 년간 데이터를 표현하기 위한 기본 함수 

// 벤치 마크 데이를 기준으로 현재 요일을 구할 수 있음
// 요일은 어떠한 경우에서든 7의 배수로 나오기 때문에 그 사이의 날짜를 구하고 7로 나누면 됨
// 아래 기준은 월요일이다.
let benchmarkDay = {
    year: 1900, 
    month: 1, 
    day: 1,
    hour: 00, 
    minites: 00
}; 
      
// 오늘 날짜를 반환함. Date 객체를 사용하여 구현, month는 0~11로 표현되어 1을 더하여야 한다.
function getToday(){

    let date = new Date();
    let year = date.getFullYear();
    let month = (1 + date.getMonth());
    let day = date.getDate();
    let hour = ("0" + date.getHours()).slice(-2);
    let minute = ("0" + date.getMinutes()).slice(-2);

    let today = {
        year: year, 
        month: month, 
        day: day,
        hour: hour, 
        minute: minute	
    }
    return today;
}
        
// 오늘 데이트를 스트링으로 변환 
function getTodayDateString(){
    let date = new Date();
    let year = date.getFullYear();
    let month = (1 + date.getMonth());
    let day;
    if(date.getDate()<10){
        day = "0"+date.getDate();
    }
    else{
        day = date.getDate();
    }

    let str = year+"-"+month+"-"+day;
    return str;
}

 // 날짜를 직접 매개변수에 넣으면 해당 데이터 형식을 리턴해줌 
function getThisDay(year, month, day, hour, minute){

    let thisDay = {
        year: year, 
        month: month, 
        day: day,
        hour: hour, 
        minute: minute
    }

    return thisDay;
}

// 오늘 요일을 구함 + 매개 변수 입력하면 특정 요일 구함 getThisDay()
// 요일은 7의 배수이므로 구성이나 간격이 변하지 않음
// 결과적으로 윤년만 정확히 계산하면 날짜에 따른 요일은 반드시 구할 수 있음.
function getYoil(date){
    let yoil; 
    let days = 0;

    if(typeof(date)!="undefined"||date!=null){
        let months = getMonthStructure();
        let today = date;

        for(let i = benchmarkDay.year; i<today.year; i++){
            let month = getMonthStructure(i);

            for(let j=0; j<month.length; j++){
                days += month[j];
            } 
        }

        for(let i = benchmarkDay.month; i<today.month; i++){
            let month = getMonthStructure(today.year);
            days += month[i-1];
        }

        for(let i = benchmarkDay.day; i<today.day; i++){
            days+=1;
        }

        // 7로 나눠지는 경우, 벤치마크데이의 요일과 동일함 
        if(days%7==0){
            yoil = monthDayTitle[1];
        }
        else if(days%7==1){
            yoil = monthDayTitle[2];
        }
        else if(days%7==2){
            yoil = monthDayTitle[3];
        }
        else if(days%7==3){
            yoil = monthDayTitle[4];
        }
        else if(days%7==4){
            yoil = monthDayTitle[5];
        }
        else if(days%7==5){
            yoil = monthDayTitle[6];
        }
        else if(days%7==6){
            yoil = monthDayTitle[0];
        }
    }
    else{
        let months = getMonthStructure();
        let today = getToday();

        for(let i = benchmarkDay.year; i<today.year; i++){
            let month = getMonthStructure(i);

            for(let j=0; j<month.length; j++){
                days += month[j];
            } 
        }

        for(let i = benchmarkDay.month; i<today.month; i++){
            let month = getMonthStructure(today.year);
            days += month[i];
        }

        for(let i = benchmarkDay.day; i<today.day; i++){
            days+=1;
        }

        if(days%7==0){
            yoil = monthDayTitle[1];
        }
        else if(days%7==1){
            yoil = monthDayTitle[2];
        }
        else if(days%7==2){
            yoil = monthDayTitle[3];
        }
        else if(days%7==3){
            yoil = monthDayTitle[4];
        }
        else if(days%7==4){
            yoil = monthDayTitle[5];
        }
        else if(days%7==5){
            yoil = monthDayTitle[6];
        }
        else if(days%7==6){
            yoil = monthDayTitle[0];
        }
    }
    return yoil;
}

// 윤년 여부 보기
// 윤년 규칙은 4년에 한번, 100년으로 나눠지나, 100년의 나눠지나 400년으로 나눠지나에 따라 결정됨
// 해당 규칙에 따라 알고리즘을 구성함 
function getYunNyen(y){

    let yunNyen; 

    if(typeof(y)!='undefined'||y!=null){

        if(y%4==0){
            yunNyen = true;

            if(y%100==0){
                yunNyen = false;

                if(y%400==0){
                    yunNyen = true;
                }
                else{
                    yunNyen = false;
                }
            }
        }
        else{
            yunNyen=false;
        }
    }	
    else{
        let date = getToday();
        let year = date.year;

        if(year%4==0){
            yunNyen = true;

            if(year%100==0){
                yunNyen = false;

                if(year%400==0){
                    yunNyen = true;
                }
                else{
                    yunNyen = false;
                }
            }
        }
        else{
            yunNyen=false;
        }
    }
    return yunNyen;
}

 // 윤년 여뷰에따라 올해 달력 구조를 생성
// 윤년은 벤치 마크가 없어도 해당 년을 4,100,400으로 나눴을땨 결정됨
// 때문에 년도만 입력하면 해당 년도의 월 단위를 알려줌 
function getMonthStructure(year){

    let months;

    // months 는 1~12 월의 달수 표시, 배열로 확인 0 == 1월 
    if(typeof(year)!='undefined'&&year!=null){
        if(getYunNyen(year)==true){
            months = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
        }
        else{
            months = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
        }
    }
    else{
        if(getYunNyen()==true){
            months = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
        }
        else{
            months = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
        }
    }
    return months;
}

월과 년간 단위의 데이터를 조작하기 위해서는 위와 같은 함수들을 미리 정의하고 사용하는 것이 좋다.

월은 윤년의 여부에 따라 데이터가 다르기 때문에, 윤년 여부를 식별하여 윤년인 경우와 아닌 경우를 배열로 반환한다. 

요일은 기준일을 대상으로 7 단위가 얼마만큼 반복되냐에 따라 구할 수 있다. 

 

2. 모양을 표시하는 함수 

1) 레이아웃 헤더

// 달력 조작 헤더 요소 만들기
function createToHeaderLayout(){
    let v; 
    let c;
    let cc;
    let ccc;
    let cccc;

    v = document.createElement("div");
    v.classList.add("calendarHeader");

    c = document.createElement("input");
    c.setAttribute("type", "hidden");
    c.classList.add("calendarHeadDateInfo");
    v.appendChild(c);

    c = document.createElement("div");
    c.classList.add("calendarSearch");

    cc = document.createElement("div");
    cc.classList.add("calendarSearchBarLeft");

    ccc = document.createElement("div");
    ccc.classList.add("calendarSearchBarArea")
    cc.appendChild(ccc);

    cccc = document.createElement("div");
    cccc.classList.add("calendarSearchBar");
    cccc.setAttribute("contenteditable", "true");
    ccc.appendChild(cccc);

    cccc = document.createElement("div");
    cccc.classList.add("calendarSearchResult");
    ccc.appendChild(cccc);
    cc.appendChild(ccc);
    c.appendChild(cc);

    cc = document.createElement("div");
    cc.classList.add("calendarSearchBarRight");

    ccc = document.createElement("div");
    ccc.classList.add("calendarSearchBtn");
    cc.appendChild(ccc);

    ccc = document.createElement("div");
    ccc.classList.add("calendarXBtn");
    ccc.innerHTML = "X";
    cc.appendChild(ccc);

    c.appendChild(cc);
    v.appendChild(c);

    c = document.createElement("div");
    c.classList.add("calendarHeadBar");

    cc = document.createElement("div");
    cc.classList.add("calendarHeadMenu");
    cc.addEventListener("click", calendarMenu);

    ccc = document.createElement("input");
    ccc.setAttribute("type", "hidden");
    ccc.setAttribute("value", "false");
    ccc.classList.add("calendarHeadMenuFlag");
    cc.appendChild(ccc);
    c.appendChild(cc);

    cc = document.createElement("div");
    cc.classList.add("calendarHeadNow");
    cc.addEventListener("click", goCalendarToday);
    c.appendChild(cc);

    cc = document.createElement("div");
    cc.classList.add("calendarHeadPre");
    cc.addEventListener("click", goCalendarPrevious);
    c.appendChild(cc);

    cc = document.createElement("div");
    cc.classList.add("calendarHeadNext");
    cc.addEventListener("click", goCalendarNext);
    c.appendChild(cc);

    cc = document.createElement("div");
    cc.classList.add("calendarHeadDate");

    c.appendChild(cc);

    cc = document.createElement("div");
    cc.classList.add("calendarHeadSelect");
    ccc = document.createElement("select");
    ccc.classList.add("selectForm");
    cccc = document.createElement("option");
    cccc.innerHTML = "년";
    cccc.setAttribute("value", "Y");
    ccc.appendChild(cccc);

    cccc = document.createElement("option");
    cccc.innerHTML = "월";
    cccc.setAttribute("value", "M");
    cccc.setAttribute("selected", "true");
    ccc.appendChild(cccc);
    /*
    cccc = document.createElement("option");
    cccc.innerHTML = "주";
    cccc.setAttribute("value", "W");
    ccc.appendChild(cccc);
    */
    cccc = document.createElement("option");
    cccc.innerHTML = "일";
    cccc.setAttribute("value", "D");
    ccc.appendChild(cccc);
    cc.appendChild(ccc);
    c.appendChild(cc);

    cc = document.createElement("div");
    cc.classList.add("calendarHeadSearch");
    cc.addEventListener("click", visiableCalendarSearch);

    ccc = document.createElement("input");
    ccc.classList.add("calendarHeadSearchFlag");
    ccc.setAttribute("type", "hidden");
    ccc.setAttribute("value", "false");

    cc.appendChild(ccc);
    c.appendChild(cc);
    v.appendChild(c);

    return v;
}

 

2) 레이아웃 바디 영역

// 달력 만들기
function createCalanderLayout(){
    let v;

    v = document.createElement("div");
    v.classList.add("calendarDiv");

    return v;
};

// Calendar Body 영역 만들기 
function createToBodyLayout(){
    let v = document.createElement("div");
    v.classList.add("calendarBody");

    let leftBox = document.createElement("div");
    leftBox.classList.add("leftBox");
    leftBox.appendChild(createToDoLayout());
    leftBox.appendChild(createGroupLayout());

    let centerBox = document.createElement("div");
    centerBox.classList.add("centerBox");

    centerBox.appendChild(createScheduleLayout());
    centerBox.appendChild(createScheduleInfo())
    centerBox.appendChild(createCalanderLayout());

    v.appendChild(naviBox);
    v.appendChild(leftBox);
    v.appendChild(centerBox);

    return v;
}
        
// 달력 그룹 항목 만들기
function createGroupLayout(){
    let v;
    let c;
    let cc;
    let ccc;

    v = document.createElement("div");
    v.classList.add("groupDiv");

    c = document.createElement("div");
    c.classList.add("groupTitle");
    c.innerHTML = "내 그룹";
    v.appendChild(c);

    c = document.createElement("div");
    c.classList.add("groupMenu");

    cc = document.createElement("div");
    cc.classList.add("groupAdd");
    cc.addEventListener("click", addGroupBtn);

    ccc = document.createElement("input");
    ccc.setAttribute("type", "hidden");
    ccc.setAttribute("value", "false");
    ccc.classList.add("groupAddFlag");
    cc.appendChild(ccc);
    c.appendChild(cc);

    v.appendChild(c);

    c = document.createElement("div");
    c.classList.add("groupMain");
    v.appendChild(c);
    return v;
}

 

3) 달력 모양 기본 펑션

// 달력 모양 만들기
function createToCalendar(){
    let calendar = document.getElementById("calendar");
    calendar.appendChild(createToHeaderLayout());
    calendar.appendChild(createToBodyLayout()); 
    changeForm("M"); //달력 초기 모양 Month 
    createToDoListBasic();
}

// 달력 모양 변경 펑션
function changeForm(selectForm, date){
    if(selectForm=='undefined'){
        console.log('undefined');
    }
    if(typeof(date)!='undefined'||date!=null){
        if(selectForm=="Y"){
            YearForm(date);
            getGroupSchedule(userKey, getToday()); // 그룹 스케줄 항목 참고
            whatIsDateInfo(selectForm, date); // 데이트 인포는 헤더 레이아웃에 필요한 항목 
        }
        else if(selectForm=="M"){
            MonthForm(date);
            getGroupSchedule(userKey, date); // 그룹 스케줄 항목 참고
            whatIsDateInfo(selectForm, date); // 데이트 인포는 헤더 레이아웃에 필요한 항목 
        }
        else if(selectForm=="D"){
            
        }
    }
    else{
        if(selectForm=="Y"){
            YearForm();
            getGroupSchedule(userKey, getToday()); // 그룹 스케줄 항목 참고
            whatIsDateInfo(selectForm, getToday()); // 데이트 인포는 헤더 레이아웃에 필요한 항목 
        }
        else if(selectForm=="M"){
            MonthForm();
            getGroupSchedule(userKey, getToday()); // 그룹 스케줄 항목 참고
            whatIsDateInfo(selectForm, getToday()); // 데이트 인포는 헤더 레이아웃에 필요한 항목 
        }
        else if(selectForm=="D"){
          
        }
    }	
}

 

4) 월간 함수 

// 입력받는 날짜에 따라 월이 그려짐 
function MonthForm(date){
    console.log("월 모양 달력 펑션");
    if(typeof(date)!='undefined'&&date!=null){
        let div = document.getElementsByClassName("calendarDiv")[0];

        while(div.hasChildNodes()){
            div.removeChild(div.firstChild);
        }
        div.appendChild(createMonthFormElement(date));
    }
    else{
        let div = document.getElementsByClassName("calendarDiv")[0];	
        while(div.hasChildNodes()){
            div.removeChild(div.firstChild);
        }

        div.appendChild(createMonthFormElement());
    }
}
더보기
function createMonthFormElement(date){

    let v;
    let c;
    let cc;
    let ccc;
    let cccc;
    // date 매개 변수에 따라 그려야할 날짜 지정
    if(typeof(date)!='undefined'||date!=null){

        v = document.createElement("div");
        v.classList.add("calendarArea");


        c = document.createElement("div");
        c.classList.add("monthAreaHead");

        for(let i = 0; i < 7; i++){
            cc = document.createElement("div");
            cc.classList.add("monthAreaHeadTitle");
            cc.innerHTML = monthDayTitle[i];
            c.appendChild(cc);
        }

        v.appendChild(c);

        c = document.createElement("div");
        c.classList.add("monthAreaBody");

        // 월, 요일 등 데이터를 가져옴 
        let months = getMonthStructure();
        let flag = months[date.month-1];
        let yoil = getYoil(getThisDay(date.year, date.month, 1, 0, 0));
        let beforeYoil;
        let NowMonth = months[date.month-2];

        // 해당 월 1일이 무슨 요일이냐에 따라 일요일부터 몇개의 이전 요소가 그려질지를 결정할 수 있음
        // 마찬가지로 해당 월 마지막 날짜에 따라 그려져야할 데이터가 상이함
        // 결국 해당 월의 스트럭쳐(월 구조 구하는 펑션 참조)에 따라서 그 월은 그려주되, 전후로 그려야할 데이터가 상이함 

        if(NowMonth==NaN||NowMonth==null){
            NowMonth = months[11];
        }
        // 해당 월 1일 전에 그리는 요소 
        if(yoil=="월"){
            for(let i = 0; i > -1 ; i--){
                c.appendChild(monthBeforeFormDate(i, date, NowMonth));
            }
        }
        else if(yoil=="화"){
            for(let i = 1; i > -1 ; i--){
                c.appendChild(monthBeforeFormDate(i, date, NowMonth));
            }
        }
        else if(yoil=="수"){
            for(let i = 2; i > -1 ; i--){
                c.appendChild(monthBeforeFormDate(i, date, NowMonth));
            }
        }
        else if(yoil=="목"){
            for(let i = 3; i > -1 ; i--){
                c.appendChild(monthBeforeFormDate(i, date, NowMonth));
            }
        }
        else if(yoil=="금"){
            for(let i = 4; i > -1 ; i--){
                c.appendChild(monthBeforeFormDate(i, date, NowMonth));
            }
        }
        else if(yoil=="토"){
            for(let i = 5; i > -1 ; i--){
                c.appendChild(monthBeforeFormDate(i, date, NowMonth));
            }
        }
        v.appendChild(c);

        // 해당 월을 그리는 요소
        for(let i = 1; i < flag+1; i++){

            cc = document.createElement("div");
            cc.classList.add("monthBox"); 

            let thisTimeDate="";

            if(i<10){
                if(date.month<10){
                    thisTimeDate = date.year+"0"+date.month+"0"+i
                }
                else{
                    thisTimeDate = date.year+""+date.month+"0"+i
                }
            }
            else{
                if(date.month<10){
                    thisTimeDate = date.year+"0"+date.month+""+i	
                }	
                else{
                    thisTimeDate = date.year+""+date.month+""+i;
                }
            }					
            console.log("중요! 해당 위의 구문과 같이 모든 데이터를 처리하는 별도 태그가 해당 폼의 요소에 포함되어 정보를 식별해야함");
            cc.addEventListener("click", visibleSchedule);

            ccc = document.createElement("div");
            ccc.classList.add("monthBoxTitle");

            if(i==1){
                cccc = document.createElement("span");
                cccc.classList.add("monthBoxTitleBoxOne");
                cccc.innerHTML = date.month+"월 "+ i +"일";
                ccc.appendChild(cccc);
            }
            else{
                cccc = document.createElement("span");
                cccc.classList.add("monthBoxTitleBox");

                cccc.innerHTML = i +"";
                ccc.appendChild(cccc);
            }

            cccc = document.createElement("input");
            cccc.classList.add("dateTag");
            cccc.setAttribute("type", "text");

            cccc.setAttribute("value", thisTimeDate);
            ccc.appendChild(cccc);

            cc.appendChild(ccc);

            ccc = document.createElement("div");
            ccc.classList.add("monthBoxBody");

            cc.appendChild(ccc);

            c.appendChild(cc);
        }

        v.appendChild(c);

        // 31일이나 30, 28, 29일 이후에 그려야할 요소
        yoil = getYoil(getThisDay(date.year, date.month+1, 1, 0, 0));
        let afterYoil;
        NowMonth = months[date.month];

        if(yoil=="월"){
            for(let i = 1; i < 7; i++){
                c.appendChild(monthAfterFormDate(i, date));
            }
        }
        else if(yoil=="화"){
            for(let i = 1; i < 6 ; i++){
                c.appendChild(monthAfterFormDate(i, date));
            }
        }
        else if(yoil=="수"){
            for(let i = 1; i < 5 ; i++){
                c.appendChild(monthAfterFormDate(i, date));
            }
        }
        else if(yoil=="목"){
            for(let i = 1; i < 4 ; i++){
                c.appendChild(monthAfterFormDate(i, date));
            }
        }
        else if(yoil=="금"){
            for(let i = 1; i < 3 ; i++){
                c.appendChild(monthAfterFormDate(i, date));
            }
        }
        else if(yoil=="토"){
            for(let i = 1; i < 2 ; i++){
                c.appendChild(monthAfterFormDate(i, date));
            }
        }
        v.appendChild(c);
    }
    else{
        // ELSE 도 결국 같은 원리이나 날짜 데이터만 다름
        let div = document.getElementsByClassName("calendarDiv")[0];

        while(div.hasChildNodes()){
            div.removeChild(div.firstChild);

        }

        v = document.createElement("div");
        v.classList.add("calendarArea");


        c = document.createElement("div");
        c.classList.add("monthAreaHead");

        for(let i = 0; i < 7; i++){
            cc = document.createElement("div");
            cc.classList.add("monthAreaHeadTitle");
            cc.innerHTML = monthDayTitle[i];
            c.appendChild(cc);
        }

        v.appendChild(c);

        c = document.createElement("div");
        c.classList.add("monthAreaBody");

        let today = getToday();
        let months = getMonthStructure();
        let flag = months[today.month-1];
        let yoil = getYoil(getThisDay(today.year, today.month, 1, 0, 0));
        let beforeYoil;
        let NowMonth = months[today.month-2];

        if(yoil=="월"){
            for(let i = 0; i > -1 ; i--){
                c.appendChild(monthBeforeFormDate(i, getToday(), NowMonth));
            }
        }
        else if(yoil=="화"){
            for(let i = 1; i > -1 ; i--){
                c.appendChild(monthBeforeFormDate(i, getToday(), NowMonth));
            }
        }
        else if(yoil=="수"){
            for(let i = 2; i > -1 ; i--){
                c.appendChild(monthBeforeFormDate(i, getToday(), NowMonth));
            }
        }
        else if(yoil=="목"){
            for(let i = 3; i > -1 ; i--){
                c.appendChild(monthBeforeFormDate(i, getToday(), NowMonth));
            }
        }
        else if(yoil=="금"){
            for(let i = 4; i > -1 ; i--){
                c.appendChild(monthBeforeFormDate(i, getToday(), NowMonth));
            }
        }
        else if(yoil=="토"){
            for(let i = 5; i > -1 ; i--){
                c.appendChild(monthBeforeFormDate(i, getToday(), NowMonth));
            }
        }
        v.appendChild(c);

        for(let i = 1; i < flag+1; i++){

            cc = document.createElement("div");
            cc.classList.add("monthBox"); 

            let thisTimeDate = "";

            if(i<10){
                thisTimeDate = getToday().year+""+getToday().month+"0"+i
            }
            else{
                thisTimeDate = getToday().year+""+getToday().month+""+i;
            }

            cc.addEventListener("click", visibleSchedule);

            ccc = document.createElement("div");
            ccc.classList.add("monthBoxTitle");

            if(i==1){
                cccc = document.createElement("span");
                cccc.classList.add("monthBoxTitleBoxOne");
                cccc.innerHTML = getToday().month+"월 "+ i +"일";
                ccc.appendChild(cccc);
            }
            else{
                cccc = document.createElement("span");
                cccc.classList.add("monthBoxTitleBox");
                cccc.innerHTML = i +"";
                ccc.appendChild(cccc);
            }
            cccc = document.createElement("input");
            cccc.classList.add("dateTag");
            cccc.setAttribute("type", "text");

            cccc.setAttribute("value", thisTimeDate);
            ccc.appendChild(cccc);
            cc.appendChild(ccc);

            ccc = document.createElement("div");
            ccc.classList.add("monthBoxBody");

            cc.appendChild(ccc);

            c.appendChild(cc);
        }

        v.appendChild(c);

        yoil = getYoil(getThisDay(today.year, today.month+1, 1, 0, 0));
        let afterYoil;
        NowMonth = months[today.month];


        if(yoil=="월"){
            for(let i = 1; i < 7; i++){
                c.appendChild(monthAfterFormDate(i, getToday()));
            }
        }
        else if(yoil=="화"){
            for(let i = 1; i < 6 ; i++){
                c.appendChild(monthAfterFormDate(i, getToday()));
            }
        }
        else if(yoil=="수"){
            for(let i = 1; i < 5 ; i++){
                c.appendChild(monthAfterFormDate(i, getToday()));
            }
        }
        else if(yoil=="목"){
            for(let i = 1; i < 4 ; i++){
                c.appendChild(monthAfterFormDate(i, getToday()));
            }
        }
        else if(yoil=="금"){
            for(let i = 1; i < 3 ; i++){
                c.appendChild(monthAfterFormDate(i, getToday()));
            }
        }
        else if(yoil=="토"){
            for(let i = 1; i < 2 ; i++){
                c.appendChild(monthAfterFormDate(i, getToday()));
            }
        }
        v.appendChild(c);
    }
    return v;
}

// 몬스 폼에서 해당 월 마지막날 이후 ELEMENT를 그림
function monthAfterFormDate(i, date){
    if(typeof(i)=='undefined'||null){
        return;
    }
    let cc;
    let ccc;
    let cccc;

    if(date.month==12){
        cc = document.createElement("div");
        cc.classList.add("monthBox"); 

        let thisTimeDate = "";

        if(i<10){
            thisTimeDate = (date.year+1)+"0"+1+"0"+i
        }
        else{
            thisTimeDate = (date.year+1)+"0"+1+""+i;
        }
        cc.addEventListener("click", visibleSchedule);

        ccc = document.createElement("div");
        ccc.classList.add("monthBoxTitle");

        cccc = document.createElement("span");
        cccc.classList.add("monthBoxTitleBox");
        cccc.innerHTML = i +"";
        ccc.appendChild(cccc);

        cccc = document.createElement("input");
        cccc.classList.add("dateTag");
        cccc.setAttribute("type", "text");

        cccc.setAttribute("value", thisTimeDate);
        ccc.appendChild(cccc);

        cc.appendChild(ccc);

        ccc = document.createElement("div");
        ccc.classList.add("monthBoxBody");

        cc.appendChild(ccc);
    }
    else{
        cc = document.createElement("div");
        cc.classList.add("monthBox"); 

        let thisTimeDate = "";

        if(date.month<9){
            if(i<10){
                thisTimeDate = date.year+"0"+(date.month+1)+"0"+i;
            }
            else{
                thisTimeDate = date.year+"0"+(date.month+1)+""+i;
            }
        }
        else{
            if(i<10){
                thisTimeDate = date.year+""+(date.month+1)+"0"+i;
            }
            else{
                thisTimeDate = date.year+""+(date.month+1)+""+i;
            }
        }
        cc.addEventListener("click", visibleSchedule);

        ccc = document.createElement("div");
        ccc.classList.add("monthBoxTitle");

        cccc = document.createElement("span");
        cccc.classList.add("monthBoxTitleBox");
        cccc.innerHTML = i +"";
        ccc.appendChild(cccc);

        cccc = document.createElement("input");
        cccc.classList.add("dateTag");
        cccc.setAttribute("type", "text");

        cccc.setAttribute("value", thisTimeDate);
        ccc.appendChild(cccc);

        cc.appendChild(ccc);

        ccc = document.createElement("div");
        ccc.classList.add("monthBoxBody");

        cc.appendChild(ccc);
    }
    return cc;
}

// monthform에서 해당 월 1일 이전의 ELEMENT를 그림
function monthBeforeFormDate(i, date, NowMonth){

    if(typeof(i)=='undefined'||null){
        return;
    }
    let cc;
    let ccc;
    let cccc;

    if(date.month==1){

        cc = document.createElement("div");
        cc.classList.add("monthBox"); 

        let thisTimeDate = "";

        if(i>10){
            thisTimeDate = (date.year-1)+""+12+"0"+(NowMonth-i);
        }
        else{
            thisTimeDate = (date.year-1)+""+12+""+(NowMonth-i);
        }

        cc.addEventListener("click", visibleSchedule);

        ccc = document.createElement("div");
        ccc.classList.add("monthBoxTitle");

        cccc = document.createElement("span");
        cccc.classList.add("monthBoxTitleBox");
        cccc.innerHTML = (NowMonth-i) +"";
        ccc.appendChild(cccc);

        cccc = document.createElement("input");
        cccc.classList.add("dateTag");
        cccc.setAttribute("type", "text");

        cccc.setAttribute("value", thisTimeDate);
        ccc.appendChild(cccc);

        cc.appendChild(ccc);

        ccc = document.createElement("div");
        ccc.classList.add("monthBoxBody");

        cc.appendChild(ccc);
    }
    else{
        cc = document.createElement("div");
        cc.classList.add("monthBox"); 

        let thisTimeDate = "";

        if(date.month<=10){
            if(i>10){
                thisTimeDate = date.year+"0"+(date.month-1)+"0"+(NowMonth-i);
            }
            else{
                thisTimeDate = date.year+"0"+(date.month-1)+""+(NowMonth-i);
            }
        }
        else{
            if(i>10){
                thisTimeDate = date.year+""+(date.month-1)+"0"+(NowMonth-i);
            }
            else{
                thisTimeDate = date.year+""+(date.month-1)+""+(NowMonth-i);
            }
        }

        cc.addEventListener("click", visibleSchedule);

        ccc = document.createElement("div");
        ccc.classList.add("monthBoxTitle");

        cccc = document.createElement("span");
        cccc.classList.add("monthBoxTitleBox");
        cccc.innerHTML = (NowMonth-i) +"";
        ccc.appendChild(cccc);

        cccc = document.createElement("input");
        cccc.classList.add("dateTag");
        cccc.setAttribute("type", "text");

        cccc.setAttribute("value", thisTimeDate);
        ccc.appendChild(cccc);

        cc.appendChild(ccc);

        ccc = document.createElement("div");
        ccc.classList.add("monthBoxBody");

        cc.appendChild(ccc);
    }
    return cc;
}

더보기는 월간 폼을 그리는데 필요한 요소를 생성하고 반환하는 함수이다.

 

5) 연간 함수 

// 입력받는 날짜에 따라 년도가 그려짐 
function YearForm(date){
    console.log("년도 모양 달력 펑션");
    let div = document.getElementsByClassName("calendarDiv")[0];
    let divc = document.createElement("div");

    while(div.hasChildNodes()){
        div.removeChild(div.firstChild);
    }

    if(typeof(date)!='undefined'&&date!=null){
        for(let box = 1; box < 13; box++){

            let p = document.createElement("div");
            p.classList.add("yearLayoutBox")

            let c = document.createElement("div");
            c.classList.add("yearInMonthTitle");
            c.innerHTML = box+" 월";

            p.appendChild(c);

            date.month = box;

            p.appendChild(createYearFormElement(date));
            div.appendChild(p);
        }
    }
    else{
        for(let box = 1; box < 13; box++){
            let dayInfo = getToday();
            dayInfo.month = box;

            let p = document.createElement("div");
            p.classList.add("yearLayoutBox")

            let c = document.createElement("div");
            c.classList.add("yearInMonthTitle");
            c.innerHTML = box+" 월";

            p.appendChild(c);

            p.appendChild(createYearFormElement(dayInfo));
            div.appendChild(p);
        }
    }
}
더보기
// 년 폼 요소 만들기
// 년은 결국 월의 집합으로 월 폼을 참고하여 구현
// 월폼에 따른 기본 알고리즘을 채택하여 월을 반복하여 데이터를 삽입
// 12번 반복하되, before과 after 요소를 안보이게 하여 깔끔하게 구현
function createYearFormElement(date){

    let v;
    let c;
    let cc;
    let ccc;
    let cccc;

    if(typeof(date)!='undefined'||date!=null){

        v = document.createElement("div");
        v.classList.add("calendarArea");


        c = document.createElement("div");
        c.classList.add("yearAreaHead");

        for(let i = 0; i < 7; i++){
            cc = document.createElement("div");
            cc.classList.add("yearAreaHeadTitle");
            cc.innerHTML = monthDayTitle[i];
            c.appendChild(cc);
        }

        v.appendChild(c);

        c = document.createElement("div");
        c.classList.add("yearAreaBody");

        let months = getMonthStructure();
        let flag = months[date.month-1];
        let yoil = getYoil(getThisDay(date.year, date.month, 1, 0, 0));
        let beforeYoil;
        let NowMonth = months[date.month-2];

        // 배열 값이 없는 항목을 참조: 할때 실행, 말을 12로 설정함 (1월일 경우 실행됨)
        if(NowMonth==NaN||NowMonth==null){
            NowMonth = months[11];
        }

        if(yoil=="월"){
            for(let i = 0; i > -1 ; i--){
                c.appendChild(yearDummyForm());
            }
        }
        else if(yoil=="화"){
            for(let i = 1; i > -1 ; i--){
                c.appendChild(yearDummyForm());
            }
        }
        else if(yoil=="수"){
            for(let i = 2; i > -1 ; i--){
                c.appendChild(yearDummyForm());
            }
        }
        else if(yoil=="목"){
            for(let i = 3; i > -1 ; i--){
                c.appendChild(yearDummyForm());
            }
        }
        else if(yoil=="금"){
            for(let i = 4; i > -1 ; i--){
                c.appendChild(yearDummyForm());
            }
        }
        else if(yoil=="토"){
            for(let i = 5; i > -1 ; i--){
                c.appendChild(yearDummyForm());
            }
        }
        v.appendChild(c);

        for(let i = 1; i < flag+1; i++){

            cc = document.createElement("div");
            cc.classList.add("yearBox"); 
            cc.classList.add("yearBoxYoil");
            cc.addEventListener("click", yearBoxGoto)

            ccc = document.createElement("div");
            ccc.classList.add("yearBoxTitle");

            cccc = document.createElement("span");
            cccc.classList.add("yearBoxTitleBox");
            cccc.innerHTML = i +"";
            ccc.appendChild(cccc);

            cc.appendChild(ccc);

            ccc = document.createElement("div");
            ccc.classList.add("yearBoxBody");

            cccc = document.createElement("input");
            cccc.classList.add("dateTag");
            cccc.setAttribute("type", "text");

            let thisTimeDate = "";

            if(date.month<10){
                if(i<10){
                    thisTimeDate = date.year+"0"+date.month+"0"+i;
                }
                else{
                    thisTimeDate = date.year+"0"+date.month+""+i;
                }
            }
            else{
                if(i<10){
                    thisTimeDate = date.year+""+date.month+"0"+i;
                }
                else{
                    thisTimeDate = date.year+""+date.month+""+i;
                }
            }

            cccc.setAttribute("value", thisTimeDate);
            ccc.appendChild(cccc);

            cc.appendChild(ccc);

            c.appendChild(cc);
        }

        v.appendChild(c);

        yoil = getYoil(getThisDay(date.year, date.month+1, 1, 0, 0));
        let afterYoil;
        NowMonth = months[date.month];

        function yearBoxGoto(){
            let target = event.target;
            let dateTag = target.parentNode.parentNode.getElementsByClassName("dateTag")[0];

            let year = parseInt(dateTag.value.substring(0,4));
            let month = parseInt(dateTag.value.substring(4,6));
            let day = parseInt(dateTag.value.substring(6,8));

            let date = getThisDay(year, month, day, 0, 0);

            let selectForm = document.getElementsByClassName("selectForm")[0];
            let options = selectForm.getElementsByTagName("option");
            for(let i = 0; i<options.length; i++){
                if(options[i].value=="M"){
                    options[i].setAttribute("selected", "true");
                }
            }
            selectForm.value="M";
            changeForm("M", date);
        }

        // 배열 값이 없는 항목을 참조: 할때 실행, 말을 12로 설정함 (1월일 경우 실행됨)
        if(NowMonth==NaN||NowMonth==null){
            NowMonth = months[11];
        }

        if(yoil=="월"){
            for(let i = 1; i < 7; i++){
                c.appendChild(yearDummyForm());
            }
        }
        else if(yoil=="화"){
            for(let i = 1; i < 6 ; i++){
                c.appendChild(yearDummyForm());
            }
        }
        else if(yoil=="수"){
            for(let i = 1; i < 5 ; i++){
                c.appendChild(yearDummyForm());
            }
        }
        else if(yoil=="목"){
            for(let i = 1; i < 4 ; i++){
                c.appendChild(yearDummyForm());
            }
        }
        else if(yoil=="금"){
            for(let i = 1; i < 3 ; i++){
                c.appendChild(yearDummyForm());
            }
        }
        else if(yoil=="토"){
            for(let i = 1; i < 2 ; i++){
                c.appendChild(yearDummyForm());
            }
        }
        v.appendChild(c);
    }
    else{

    }
    return v;
}

// 년도의 표시되지 않으나 영역을 차지하는 폼 
// 해당 폼은 Dummy 역할을 하여 데이터를 가지고 있지도 않고 그냥 모양만 잡음
function yearDummyForm(){
    let c;
    let cc;
    let ccc;

    cc = document.createElement("div");
    cc.classList.add("yearBoxDummy"); 
    cc.classList.add("yearBoxYoil");

    ccc = document.createElement("div");
    ccc.classList.add("yearBoxTitleDummy");

    cccc = document.createElement("span");
    cccc.classList.add("yearBoxTitleBoxDummy");
    ccc.appendChild(cccc);

    cc.appendChild(ccc);

    ccc = document.createElement("div");
    ccc.classList.add("yearBoxBodyDummy");
    cc.appendChild(ccc);

    return cc;
}

더보기는 연간 폼을 그리는데 필요한 요소를 생성하고 반환하는 함수이다.

 

3. 코드 리뷰 

요소 만들기 변수

p = parent
c = child
pp = parent의 parent
cc = child의 child
ppp = parent의 parent의 parent
ccc = child의 child의 child
v = 기준 요소
v 에 특별한 의미 없이 그냥 내가 좋아해서 사용함

위의 펑션들은 공통적으로 노드 단위로 엘레먼트 노드를 형성하고, 형성한 노드를 appendChild 하는 방식으로 구성된다. 새로운 모양을 그릴때는 Child Node를 모두 지우고 그리는 방식을 사용했다. 

각 노드를 조작하기 쉽도록 위와 같은 변수 규칙을 적용하였는데, 이는 변수 명을 하나하나 지정하고 변경해가며 조작하면 변수명을 정의하고 이해하기 어렵기 때문이다. 

 

각 요소의 특성은 Class로 정의하여 식별하도록 하고, 생성 단계에서는 자식과 부모 사이만 파악하도록 하였다. 

 

각 펑션에서 데이터를 v로 return 하게 만들어 대분류가 되는 펑션에서 이를 확인하기 쉽도록 만들었다.

물론 경우에 따라서 한정된 데이터만이 들어간다면 굳이 return하지 않고, 해당 요소를 찾아 바로 부여하는 방식을 사용하기도 했다.