хуйу нас не матерятся
Понадобился мне javascript виджет с выбором даты. И так как я не люблю тащить километровые зависимости, я решил написать свой собственный велосипед.
Для начала делаем обычную хтмльку, подключаем в неё бутстрап и добавляем кнопку активации календаря и пару тестовых инпутов, куда будем выводить результат:
<html>
<head>
<title>Date picker on pure js</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
<style>
/* datepicker styles */
.datetimepicker-header {
position: relative;
background: #f00;
}
.datetimepicker-header-close {
position: absolute;
right: 1px;
top: 1px;
margin: 0;
padding: 0;
color: #fff;
cursor: pointer;
}
</style>
</head>
<body>
<div class="container">
<h1>Datetime picker test</h1>
<form>
<input type="text" id="dateInput" class="form-control"/>
<span id="datePickerWrapper"></span><a href="#" class="btn btn-primary" onclick="showCalend(this)">Open calendar</a>
</form>
</div>
</body>
</html>
После этого, нам нужно написать функцию, которая будет генерировать сам календарь, на вход она будет получать год и месяц, а возвращать будет строку, с хтмл кодом уже готового календаря:
function generateCalendar(month, year) {
let result = '';
let curTime = new Date();
let months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];
result += '<div class="datetimepicker-wrapper">';
result += '<div class="datetimepicker-header">';
result += '<h1 class="datetimepicker-title">Select date</h1>';
result += '<h1 class="datetimepicker-header-close" onclick="closeDatePicker(this)">X</h1>';
result += '</div>';
result += '<div class="datetimepicker-body">';
result += '<div class="row">';
result += '<div class="col">';
result += '<select class="form-control datetimepicker-month" onchange="datePickerMonthChange(this)">';
for(let index in months)
if( index == month )
result += `<option value="${index}" selected>${months[ index ]}</option>`;
else
result += `<option value="${index}">${months[ index ]}</option>`;
result += '</select>';
result += '</div>';
result += '<div class="col">';
result += '<input type="number" class="form-control datetimepicker-year" value="' + year + '" onchange="datePickerYearChange(this)"/>';
result += '</div>';
result += '</div>';
result += '<div class="row"><div class="col-sm-12">';
result += '<table class="table"><tbody class="datetimepicker-datetablebody"></tbody></table>';
result += '</div></div>';
result += '</div>';
result += '</div>';
result += '';
return result;
}
Сейчас наш календарь буддет выглядеть так:
У нас есть выбор месяца, выбор года, нужно сделать функцию которая будет генерировать таблицу с выбором даты:
function generateDateTableBody(month, year) {
let result = '';
let counter = 0;
let curDate = new Date(year, month, 1, 0, 0, 0, 0);
let startDatOffset = curDate.getDay() * -1 + 2;
for(let week=0; week<6; week++) {
result += '<tr>';
for(let day=0; day<7; day++) {
let d = new Date(year, month, counter + startDatOffset, 0, 0, 0, 0);
if( d.getMonth() == month )
result += `<td class="datetimepicker-curmonth" data-month="${d.getMonth()}" data-year="${d.getFullYear()}" onclick="dayClick(this)">${d.getDate()}</td>`;
else
result += `<td class="datetimepicker-not-curmonth" data-month="${d.getMonth()}" data-year="${d.getFullYear()}" onclick="dayClick(this)">${d.getDate()}</td>`;
counter ++;
}
result += '</tr>';
}
return result;
}
Сразу же докидываем стилей, чтобы выделить текущий месяц, а так же чтобы подсветить дату, при наведении мышки:
.datetimepicker-curmonth {
text-align: center;
cursor: pointer;
background: #ddd;
font-weight: bold;
}
.datetimepicker-curmonth:hover {
background: #ccf;
}
.datetimepicker-not-curmonth {
text-align: center;
cursor: pointer;
background: #eee;
}
.datetimepicker-not-curmonth:hover {
background: #ccf;
}
Подключаем эту функцию в closeDatePicker:
...
result += '<table class="table"><tbody class="datetimepicker-datetablebody">' + generateDateTableBody(month, year) + '</tbody></table>';
...
И получаем уже почти готовый календарь:
Нам осталось сделать обработчики событий. Для выбора месяца и выбора года:
function datePickerMonthChange(e) {
let month = e.options[e.selectedIndex].value;
let wrapper = e.closest(`.datetimepicker-wrapper`);
let tableBody = wrapper.getElementsByClassName('datetimepicker-datetablebody')[ 0 ];
let yearSelector = wrapper.getElementsByClassName('datetimepicker-year')[ 0 ];
tableBody.innerHTML = generateDateTableBody(parseInt(month), parseInt(yearSelector.value));
}
function datePickerYearChange(e) {
let year = parseInt(e.value);
let wrapper = e.closest(`.datetimepicker-wrapper`);
let tableBody = wrapper.getElementsByClassName('datetimepicker-datetablebody')[ 0 ];
let monthSelector = wrapper.getElementsByClassName('datetimepicker-month')[ 0 ];
tableBody.innerHTML =generateDateTableBody(parseInt(monthSelector.options[monthSelector.selectedIndex].value), year);
}
Работают они так. datePickerYearChange получает ссылку на дом элемент, input type="number" class="form-control datetimepicker-year"
в котором произошло событие. Далее имея доступ к элементу, котоый находится внутри календаря, можем найти основного родителя, в котром находится календарь, с помощью
let wrapper = e.closest(`.datetimepicker-wrapper`);
И уже имея доступ к родителю, с помощью getElementsByClassName находим место куда нужно воткнуть перегенерированный календарь и инпут с месяцем. Ровно то же самое происходит в datePickerMonthChange
И добавляем последний обработчик события, уже клик по дню месяца, в табличке:
function dayClick(e) {
let wrapper = e.closest(`.datetimepicker-wrapper`);
let curDate = new Date(parseInt(e.dataset.year), parseInt(e.dataset.month), parseInt(e.innerHTML), 0, 0, 0, 0);
wrapper.parentNode.selectCB(curDate);
wrapper.remove();
}
wrapper.parentNode.selectCB(curDate); сейчас не сработает, нам нужно ещё написать функцию, которая активирует календарь:
function datePicker(wrapper, cb, startDate=null) {
let curDate = new Date();
if( startDate !== null )
curDate = new Date(startDate);
wrapper.innerHTML = generateCalendar(curDate.getMonth(), curDate.getFullYear());
wrapper.selectCB = cb;
}
На вход она получает дом элемент куда нужно воткнуть календарь, второй параметр- это функция колбэк, которая сработает, когда пользователь выберит дату. Эту функцию, мы сохраняем в родительский элемент wrapper.selectCB = cb
. И в dayClick у нас будет доступен этот колбэк.
В завершении добавляем обработчик кнопки закрытия календаря и активации календаря:
function closeDatePicker(e) {
e.closest(`.datetimepicker-wrapper`).remove();
}
function calendarCB(date) {
document.getElementById('dateInput').value = date;
}
function showCalend() {
datePicker(document.getElementById('datePickerWrapper'), calendarCB);
}
Готовый пример можно посмотреть тут. Ну а т.к тут весь проект - это всего 1 файл, то гитхаба не будет, нет смысла.
Бонусом видео, как оно делалось:
Ну так код открыт, сделай фикс, отправь пулл-реквест, я всё помержу и напишу, что тов. Роман прислал замечательный патч и ваще хороший человек. Или всё что можем это только crtl+c/ctrl+v ??
а как сделать что бы изначальный пользователь мог изменять дату, а второй ток мог смотреть
>679
Очень нечёткий вопрос. Если не нужно чтобы юзер мог изменять дату, просто не показвай календарь. Покажи дату в стандартном инпуте со статусом "ридонли". Но тут ещё есть много вопросов, например, а как узнать, что юзеру нужно/не нужно давать выбрать дату? Об этом как-то должен фронт от бэка узнать. Финальную проверку, что там пользователь натыкал в браузере должен опять же бэк проверять. Итд.
Насчёт проблемы со 2м числом - мне помогло в generateDateTableBody, когда идём циклом по дням добавить проверку if (startDatOffset === 2) startDatOffset = -5;
открыл пример и вижу что например в декабре не отображается первый день месяца, то есть календарь начинается со 2го числа, фуфел короче