JS) 모바일 웹용 dateTimePicker 만들어보기

청룡·2022년 3월 30일
0

이번 프로젝트는 datetimepicker range를 쓰려했으나 ui적 이나 사용면이나 불편한게 많은거 같아서 갈아 엎고 예쁘게 만들어보기로 했다....

급하게 하느라 코드정리할 시간이 없지만 나중에 쉽게 보고 정리하기 위해 블로그에 남겨둡니다.
미래의 나 정리해라!

결과물~``

/**
 *  작성일: 2022-03-30
 *  설명: user용 데이트타임 픽커
 * **/

const drawStartDateCus = document.getElementById('drawStartDateCus');
const drawStartHoursCus = document.getElementById('drawStartHoursCus');
const drawEndDateCus = document.getElementById('drawEndDateCus');
const drawEndHoursCus = document.getElementById('drawEndHoursCus');

let startDate;
let startHours;
let endDate;
let endHours;

const container = document.querySelector('.scrolly-container');
const yinner = document.querySelector('.yinner');
const customHours = document.querySelectorAll('.start');
const containerx = document.querySelector('.scrollx-container');
const xinner = document.querySelector('.xinner');
const WEEKDAY = ['일', '월', '화', '수', '목', '금', '토'];

// 한달 날짜 가져오기
let res_day = [];
let endResDay = [];
let today = new Date();
const afterMon = new Date(today.setMonth(today.getMonth() + 1));
today = new Date();
while (today.getTime() <= afterMon.getTime()) {
    res_day.push(today.getTime())
    today.setDate(today.getDate() + 1)
}
// 날짜 생성하기
res_day.forEach(e => {
    html = ``;
    html += `<div class="customDay startDay">`
    html += `    <label>`
    html += `        <input type="radio" name="startDay" style="display: none;" value="${moment(e).format('YYYY-MM-DD')}">`
    html += `        <p>${WEEKDAY[new Date(e).getDay()]}</p>`
    html += `        <p>${new Date(e).getDate()}</p>`
    html += `    </label>`
    html += `</div>`
    $('#startDate').append(html);
})


res_day.forEach(e => {
    html = ``;
    html += `<div class="customDay endDay">`
    html += `    <label>`
    html += `        <input type="radio" name="endDay" style="display: none;" value="${moment(e).format('YYYY-MM-DD')}">`
    html += `        <p>${WEEKDAY[new Date(e).getDay()]}</p>`
    html += `        <p>${new Date(e).getDate()}</p>`
    html += `    </label>`
    html += `</div>`
    $('#endDate').append(html);
})

const customDayStart = document.querySelectorAll('.startDay');
let startPos = 0;
let offset = 0;
let curPos = 0;
const cnWidth = customHours[0].clientHeight;

container.addEventListener('touchstart', (e) => {
    startPos = e.touches[0].pageY;  // 터치한 Y 좌표
});

container.addEventListener('touchmove', (e) => {
    document.getElementById('customDatePicker').style.overflowY = "hidden";
    yinner.style.transitionDuration = '0ms';
    offset = curPos + (e.targetTouches[0].pageY - startPos); // 이동중인 x좌표
    yinner.style.transform = `translate3d(0px, ${offset}px, 0px)`;
    customHours.forEach(e => {
        if (e.classList.contains('selectcustomHour')) {
            e.classList.remove('selectcustomHour')
        }
    })
    let moveHours = Math.round(offset / cnWidth) * cnWidth
    if (moveHours <= -(cnWidth * (24 - 1))) {
        moveHours = -(cnWidth * (24 - 1))
    }
    if (moveHours >= 0) {
        moveHours = 0;
    }
    customHours[-(moveHours / cnWidth - 1)].classList.add('selectcustomHour');
});

container.addEventListener('touchend', (e) => {
    document.getElementById('customDatePicker').style.removeProperty('overflow-y')
    const sum = curPos + (e.changedTouches[0].pageY - startPos); //바낀 x좌표
    let finalX = Math.round(sum / cnWidth) * cnWidth;
// 0은 첫단 영역의 시작이다  이동될 수록 -
    if (finalX >= 0) {
        finalX = 0;
    }

// 끝단 영역보다 더 작으면 끝단으로
    else if (finalX <= -(cnWidth * (24 - 1))) {
        finalX = -(cnWidth * (24 - 1));
    }

    if (document.querySelector('input[name="startDay"]:checked')) {
        if (document.querySelector('input[name="startDay"]:checked').value == document.querySelectorAll('input[name="startDay"]')[0].value) {
            if (-(finalX / cnWidth ) <= new Date().getHours()) {
                finalX = (-(new Date().getHours())) * cnWidth;
            }
        }
    }
    let hoursIndex = -(finalX / cnWidth - 1) - 1;
    if (hoursIndex < 0) {
        hoursIndex = 0
    } else if (hoursIndex > customHours.length - 1) {
        hoursIndex = customHours.length - 1;
    }
    hoursIndex += 1;
    customHours[hoursIndex].classList.add('selectcustomHour');
    yinner.style.transform = `translate3d(0px, ${finalX}px, 0px)`;
    yinner.style.transitionDuration = '300ms';
    curPos = finalX;
    startHours = (-(finalX / cnWidth )) /// 시간 값
    drawStartHoursCus.innerText = startHours + "시";
    if (startHours == 23 && startDate == endDate) {
        const indexNum = Array.from(document.querySelectorAll('input[name="startDay"]')).indexOf(document.querySelector('input[name="startDay"]:checked'))
        costomDayInputEnd[indexNum + 1].addEventListener('click', endDayPiclk(costomDayInputEnd[indexNum + 1])) // 다음날짜로 자동 픽킹
        costomDayInputEnd[indexNum + 1].setAttribute('checked', true); // radio 체크
    } else {
        if (startDate == endDate && startHours >= endHours) {
            endHourPick();
        }
    }
    caculateDateHours();
});

let startPosx = 0;
let offsetx = 0;
let curPosx = 0;
const cnWidthx = customDayStart[0].clientWidth;

containerx.addEventListener('touchstart', (e) => {
    startPosx = e.touches[0].pageX;  // 터치한 Y 좌표
});

containerx.addEventListener('touchmove', (e) => {
    xinner.style.transitionDuration = '0ms';
    offsetx = curPosx + (e.targetTouches[0].pageX - startPosx); // 이동중인 x좌표
    xinner.style.transform = `translate3d(${offsetx}px, 0px, 0px)`;
});

containerx.addEventListener('touchend', (e) => {
    const sum = curPosx + (e.changedTouches[0].pageX - startPosx); //바낀 x좌표
    let finalX = Math.round(sum / cnWidthx) * cnWidthx;
// 0은 첫단 영역의 시작이다  이동될 수록 -
    if (finalX >= 0) {
        finalX = 0;
    }

// 끝단 영역보다 더 작으면 끝단으로
    else if (finalX <= -(cnWidthx * (res_day.length - 1))) {
        finalX = -(cnWidthx * (res_day.length - 1));
    }
    xinner.style.transform = `translate3d(${finalX}px, 0px, 0px)`;
    xinner.style.transitionDuration = '300ms';
    curPosx = finalX;
});

const costomDayInput = document.querySelectorAll('input[name="startDay"]');
costomDayInput.forEach((e, index) => {
    e.addEventListener('click', e => {
        if (endDate != null && new Date(e.target.value) > new Date(endDate)) {
            costomDayInputEnd[index].addEventListener('click', endDayPiclk(costomDayInputEnd[index])) // 다음날짜로 자동 픽킹
            costomDayInput[index].checked = "checked";
        }
        if (e.target.value == endDate && startHours == 23) {
            costomDayInputEnd[index + 1].addEventListener('click', endDayPiclk(costomDayInputEnd[index + 1])) // 다음날짜로 자동 픽킹
            costomDayInputEnd[index + 1].checked = "checked";
        }
        customDayStart.forEach(e => {
            e.classList.remove('selectcustomDay');
        })
        e.target.parentNode.parentNode.classList.add('selectcustomDay');
        startDate = e.target.value;
        drawStartDateCus.innerText = startDate;
        if (startHours > endHours && startDate == endDate) {
            endHourPick();
        }
        if (startHours < new Date().getHours()+1 && startDate == document.querySelectorAll('input[name="startDay"]')[0].value) {
            containerx.addEventListener('touchend', startHourPick(e))
        }
        caculateDateHours()
    })
})
// 종료시간 관련 변수

let endStartPos = 0;
let endOffset = 0;
let endCurPos = 0;
let endStartPosx = 0;
let endOffsetx = 0;
let endCurPosx = 0;

const containerEnd = document.querySelector('#endTimeBoxY');
const yinnerEnd = document.querySelector('#endHours');
const containerxEnd = document.querySelector('#endTimeBoxX');
const xinnerEnd = document.getElementById('endDate');
const customHoursEnd = document.querySelectorAll('.end');

containerEnd.addEventListener('touchstart', (e) => {
    endStartPos = e.touches[0].pageY;  // 터치한 Y 좌표
});

containerEnd.addEventListener('touchmove', (e) => {
    document.getElementById('customDatePicker').style.overflowY = "hidden";
    yinnerEnd.style.transitionDuration = '0ms';
    endOffset = endCurPos + (e.targetTouches[0].pageY - endStartPos); // 이동중인 x좌표
    yinnerEnd.style.transform = `translate3d(0px, ${endOffset}px, 0px)`;
    customHoursEnd.forEach(e => {
        if (e.classList.contains('selectcustomHour')) {
            e.classList.remove('selectcustomHour')
        }
    })
    let moveHours = Math.round(endOffset / cnWidth) * cnWidth
    if (moveHours <= -(cnWidth * (24 - 1))) {
        moveHours = -(cnWidth * (24 - 1))
    }
    if (moveHours >= 0) {
        moveHours = 0;
    }
    customHoursEnd[-(moveHours / cnWidth - 1)].classList.add('selectcustomHour');
});

containerEnd.addEventListener('touchend', (e) => {
    const sum = endCurPos + (e.changedTouches[0].pageY - endStartPos); //바낀 x좌표
    let finalX = Math.round(sum / cnWidth) * cnWidth;
// 0은 첫단 영역의 시작이다  이동될 수록 -
    if (finalX >= 0) {
        finalX = 0;
    }

// 끝단 영역보다 더 작으면 끝단으로
    else if (finalX <= -(cnWidth * (24 - 1))) {
        finalX = -(cnWidth * (24 - 1));
    }

    if (document.querySelector('input[name="endDay"]:checked')) {
        if (startHours != null && startDate != null && endDate != null && startDate == endDate) {
            if (-(finalX / cnWidth - 1) <= startHours) {
                finalX = (-(startHours)-1) * cnWidth;
            }
        }
    }
    let hoursIndex = -(finalX / cnWidth - 1) - 1;
    if (hoursIndex < 0) {
        hoursIndex = 0
    } else if (hoursIndex > customHoursEnd.length - 1) {
        hoursIndex = customHoursEnd.length - 1;
    }
    hoursIndex += 1;
    customHoursEnd[hoursIndex].classList.add('selectcustomHour');
    yinnerEnd.style.transform = `translate3d(0px, ${finalX}px, 0px)`;
    yinnerEnd.style.transitionDuration = '300ms';
    endCurPos = finalX;
    endHours = (-(finalX / cnWidth )) /// 시간 값
    drawEndHoursCus.innerText = endHours + "시";
    document.getElementById('customDatePicker').style.removeProperty('overflow-y')
    caculateDateHours()
});

containerxEnd.addEventListener('touchstart', (e) => {
    endStartPosx = e.touches[0].pageX;  // 터치한 Y 좌표
});

containerxEnd.addEventListener('touchmove', (e) => {
    xinnerEnd.style.transitionDuration = '0ms';
    endOffsetx = endCurPosx + (e.targetTouches[0].pageX - endStartPosx); // 이동중인 x좌표
    xinnerEnd.style.transform = `translate3d(${endOffsetx}px, 0px, 0px)`;
});

containerxEnd.addEventListener('touchend', (e) => {
    const sum = endCurPosx + (e.changedTouches[0].pageX - endStartPosx); //바낀 x좌표
    let finalX = Math.round(sum / cnWidthx) * cnWidthx;
// 0은 첫단 영역의 시작이다  이동될 수록 -
    if (finalX >= 0) {
        finalX = 0;
    }

// 끝단 영역보다 더 작으면 끝단으로
    else if (finalX <= -(cnWidthx * (res_day.length - 1))) {
        finalX = -(cnWidthx * (res_day.length - 1));
    }
    xinnerEnd.style.transform = `translate3d(${finalX}px, 0px, 0px)`;
    xinnerEnd.style.transitionDuration = '300ms';
    endCurPosx = finalX;
});
const customDayEnd = document.querySelectorAll('.endDay');
const costomDayInputEnd = document.querySelectorAll('input[name="endDay"]');
costomDayInputEnd.forEach((e, index) => {
    e.addEventListener('click', e => {
        if (startDate != null && new Date(e.target.value) < new Date(startDate)) {
            costomDayInput[index].addEventListener('click', startDayPick(costomDayInput[index]))
            costomDayInput[index].checked = "checked";
            startHourPick()
        }
        if (e.target.value == startDate && startHours == 23) {
            costomDayInputEnd[index + 1].addEventListener('click', endDayPiclk(costomDayInputEnd[index + 1])) // 다음날짜로 자동 픽킹
            costomDayInputEnd[index + 1].checked = "checked";
            return false;
        }
        customDayEnd.forEach(e => {
            e.classList.remove('selectcustomDay');
        })
        e.target.parentNode.parentNode.classList.add('selectcustomDay');
        endDate = e.target.value;
        drawEndDateCus.innerText = endDate;
        if (startHours >= endHours && startDate == endDate) {
            endHourPick();
        }
        caculateDateHours()
    })
})


window.addEventListener('DOMContentLoaded', e => {
    costomDayInput[0].addEventListener('click', startDayPick(costomDayInput[0])) // 현재날짜로 자동 픽킹
    costomDayInput[0].checked = "checked";
    costomDayInputEnd[0].addEventListener('click', endDayPiclk(costomDayInputEnd[1])) // 다음날짜로 자동 픽킹
    costomDayInputEnd[1].checked = "checked";
    containerx.addEventListener('touchend', startHourPick(e)); // 기본 한시인데 오늘날짜면 현재시간 +1
    containerEnd.addEventListener('touchend', endHourPick(e)); // 상동
    caculateDateHours();
})

function startDayPick(e) {
    if (endDate != null && new Date(e.value) > new Date(endDate)) {
        alert("종료시간 보다 뒤에 올 수 없습니다")
        return false;
    }
    customDayStart.forEach(e => {
        e.classList.remove('selectcustomDay');
    })
    e.parentNode.parentNode.classList.add('selectcustomDay');
    startDate = e.value;
    drawStartDateCus.innerText = startDate;
    if (startHours < new Date().getHours()+1 && startDate == document.querySelectorAll('input[name="startDay"]')[0].value) {
        containerx.addEventListener('touchend', startHourPick())
    }
}

function endDayPiclk(e) {
    if (startDate != null && new Date(e.value) < new Date(startDate)) {
        alert("시작시간 보다 앞에 올 수 없습니다")
        return false;
    }
    customDayEnd.forEach(e => {
        e.classList.remove('selectcustomDay');
    })
    e.parentNode.parentNode.classList.add('selectcustomDay');
    endDate = e.value;
    drawEndDateCus.innerText = endDate;
}

function startHourPick(e) {
    let finalX = 0;
    if (document.querySelector('input[name="startDay"]:checked')) {
        if (document.querySelector('input[name="startDay"]:checked').value == document.querySelectorAll('input[name="startDay"]')[0].value) {
            if (-(finalX / cnWidth - 1) <= new Date().getHours()) {
                finalX = (-(new Date().getHours())) * cnWidth;
            }
        }
    }
    let hoursIndex = -(finalX / cnWidth - 1) - 1;
    if (hoursIndex < 0) {
        hoursIndex = 0
    } else if (hoursIndex > customHours.length - 1) {
        hoursIndex = customHours.length - 1;
    }
    customHours.forEach(e => {
        if (e.classList.contains('selectcustomHour')) {
            e.classList.remove('selectcustomHour')
        }
    })
    hoursIndex += 1;
    customHours[hoursIndex].classList.add('selectcustomHour');
    yinner.style.transform = `translate3d(0px, ${finalX}px, 0px)`;
    yinner.style.transitionDuration = '300ms';
    curPos = finalX;
    startHours = (-(finalX / cnWidth )) /// 시간 값
    drawStartHoursCus.innerText = startHours + "시";
}

function endHourPick(e) {
    let finalX = 0;
    if (document.querySelector('input[name="endDay"]:checked')) {
        if (startHours != null && startDate != null && endDate != null && startDate == endDate) {
            if (-(finalX / cnWidth - 1) <= startHours) {
                finalX = (-(startHours)-1) * cnWidth;
            }
        }
    }
    let hoursIndex = -(finalX / cnWidth - 1) - 1;
    if (hoursIndex < 0) {
        hoursIndex = 0
    } else if (hoursIndex > customHoursEnd.length - 1) {
        hoursIndex = customHoursEnd.length - 1;
    }
    customHoursEnd.forEach(e => {
        if (e.classList.contains('selectcustomHour')) {
            e.classList.remove('selectcustomHour')
        }
    })
    hoursIndex += 1;
    customHoursEnd[hoursIndex].classList.add('selectcustomHour');
    yinnerEnd.style.transform = `translate3d(0px, ${finalX}px, 0px)`;
    yinnerEnd.style.transitionDuration = '300ms';
    endCurPos = finalX;
    endHours = (-(finalX / cnWidth )) /// 시간 값
    drawEndHoursCus.innerText = endHours + "시";
}

const resultHours = document.getElementById('resultHours');

function caculateDateHours() {
    if (startDate, startHours, endDate, endHours != null) {
        const finalstartdate = new Date(startDate + " " + startHours + ":00");
        const finalendDate = new Date(endDate + " " + endHours + ":00");
        const resultDate = finalendDate - finalstartdate;
        resultHours.innerText = resultDate / (1000 * 60 * 60) + "시간";
        reservationDate.value = startDate + " " + startHours + "시 ~ " + endDate + " " + endHours + "시"
    }
}

중복 코드가 상당히 많고 유효성 검사를 하느라 땜빵질을 너무 많이 한느낌이 든다...ㅠㅠ 하지만 기능적으로 잘 작동해서 나름 뿌듯했다.

상당부분은 이전글인 스와이프를 이용했다. 바뀐건 x축이냐 y축이냐 정도
또하나는 가운데로 옮기고 싶을때 빈 공간을 추가했다.(이건 나만 아는 소리)

profile
새대갈🕊️에서 돌고래🐬

0개의 댓글