Недавно возникла потребность создать календарь событий, где каждая дата в календаре будет подсвечена ссылкой, если какое-нибудь событие присутствует для каждого числа. Если мне разрешат оставить ссылку, здесь демонстрация работы календаря.
Задача вроде бы не сложная, но среди немногочисленных решений в интернете я не нашел подходящего по следующим причинам: слишком сложный и непонятный код, медленные запросы к БД (это особенно ощущается, если в базе много записей), использование библиотеки jQuery, к которой я отношусь не очень хорошо.
Итак, к плюсам моего календаря можно отнесли следующее:
- Весь код помещается в 200 строчек и состоит из одного файла, который подключается через include
- Скрипт состоит из чистого php + javascript без использования библиотеки jQuery
- Используются простые и оптимизированные запросы к БД
- Подгрузка следующего (предыдущего) месяца происходит через AJAX
Теперь обо всем по-порядку.
Логика
Календарь генерируется средствами php для текущего месяца. Для каждого дня проверяем нет ли записей в БД, если есть, — формируем ссылку на событие. Дописываем javascript код для перелистывание месяцев, который обращается к скрипту через ajax. Задача усложняется тем, что события растянуты во времени, то есть, начинаются в один день, а заканчиваются через несколько дней или даже месяцев. На всем временном промежутке существование события нужно его подсветить ссылкой для каждого дня.
Генерируем календарь на PHP
<?php
//Проверяем, пришел ли запрос на конкретную дату. Если нет, берем текущую дату.
if (isset($_REQUEST['date']))
{
//Проверяем, не пришло ли чего лишнего...
$pattern = "/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/";
if (preg_match($pattern, $_REQUEST['date'])) {
$date = $_REQUEST['date'];
} else {
die('Неправильный параметр');
}
}
else
{
$date=date("Y-m-d");
}
$sd = explode("-", $date);
$year = $sd[0];
$month = $sd[1];
$day = $sd[2];
// Вычисляем число дней в текущем месяце
$dayofmonth = date('t',
mktime(0, 0, 0, $month, 1, $year));
//Готовим запрос к БД
$todate = "$year-$month-$dayofmonth";
$fromdate = "$year-$month-01";
$query = "SELECT date,enddate from sobytia WHERE startdate<='$todate' AND enddate>=$fromdate";
$res_db = $db->sql($query);
Таким образом, мы выбрали все записи, которые есть в текущем месяце.
Дальше самое интересное: заполняем обходочный массив. Для того, чтобы не крутить лишний раз все заново, если находится соответствие, элемент массива удаляется и следующий цикл имеет меньше итераций.
$d = array();$k=array();
for($i = 1; $i<=$dayofmonth; $i++){
$k[$i] = $i;
}
$i=0;
while ($a = mysqli_fetch_row($res_db))
{
//for($i = 1; $i<=$dayofmonth; $i++){
foreach ($k as $i)
{ //Добавление 0 к дате
if($i<10) $cd = "$year-$month-0".$i; else $cd = "$year-$month-$i";
if ($cd >= $a[0] && $cd <= $a[1])
{
$d[$i] = $cd;
unset($k[$i]);
}
}
}
Собственно, сам календарь:
// Счётчик для дней месяца
$day_count = 1;
// 1. Первая неделя
$num = 0;
for($i = 0; $i < 7; $i++)
{
// Вычисляем номер дня недели для числа
$dayofweek = date('w',
mktime(0, 0, 0, $month, $day_count, $year));
// Приводим к числа к формату 1 - понедельник, ..., 6 - суббота
$dayofweek = $dayofweek - 1;
if($dayofweek == -1) $dayofweek = 6;
if($dayofweek == $i)
{
// Если дни недели совпадают,
// заполняем массив $week
// числами месяца
$week[$num][$i] = $day_count;
$day_count++;
}
else
{
$week[$num][$i] = "";
}
}
// 2. Последующие недели месяца
while(true)
{
$num++;
for($i = 0; $i < 7; $i++)
{
$week[$num][$i] = $day_count;
$day_count++;
// Если достигли конца месяца - выходим
// из цикла
if($day_count > $dayofmonth) break;
}
// Если достигли конца месяца - выходим
// из цикла
if($day_count > $dayofmonth) break;
}
// 3. Выводим содержимое массива $week
// в виде календаря
// Выводим таблицу
echo '<table id="calendar">';
//заголовок
$rusdays = array('ПН','ВТ','СР','ЧТ','ПТ','СБ','ВС');
$rusmonth = array('Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь');
echo '<thead>
<tr>
<td onclick="monthf('prev');"><</td>
<td colspan="5">'.$rusmonth[$month-1].', '.$year.'</td>
<td onclick="monthf('next');">></td>
</tr>';
echo '<tr>';
foreach ($rusdays as $rusday){
echo '<td>'.$rusday.'</td>';
}
echo '</tr>';
echo '</thead>';
//тело календаря
for($i = 0; $i < count($week); $i++)
{
echo "<tr>";
for($j = 0; $j < 7; $j++)
{
if(!empty($week[$i][$j]))
{
// Если имеем дело с выбраной датой подсвечиваем ee
if($week[$i][$j]==$day)
{
echo '<td class="today">';
}
else
{
echo '<td>';
}
// Если запись в базе за текущую дату есть, делаем ссылку
if($d[$week[$i][$j]])
{
echo '<a href="/afisha/'.$d[$week[$i][$j]].'/">'.$week[$i][$j].'</a>';
}
else
{
echo $week[$i][$j];
}
echo '</td>';
}
else echo "<td> </td>";
}
echo "</tr>";
}
?>
Javascript код для перематывание месяцев
Он немного упрощен для наглядности (отсутствуют эффекты скольжения):
<script>
var mon = parseInt("<?php echo $month; ?>");
var year = parseInt(<?php echo $year; ?>);
function monthf(pn){
if (pn == 'next'){
mon++;
}else if (pn == 'prev'){
mon--;
}else{
alert('Неправильный параметр');
return false;
}
if (mon > 12){
year ++;
mon = 1;
}
if (mon < 1){
year --;
mon = 12;
}
if ((mon < 10) && (mon >= 1)){
mon = '0'+mon;
}
var nextDate = year+'-'+mon+'-00';
var ajaxaddr = "путь_к_текущему_скрипту?date='+nextDate;
var http = new XMLHttpRequest();
if (http) {
http.open('get', ajaxaddr);
http.onreadystatechange = function () {
if(http.readyState == 4){
if (http.status == 200) {
document.getElementById('calendar').innerHTML = http.responseText;
}
}
}
http.send(null);
}
}
</script>
Закрываем тег таблицы:
<?php
echo "</table>";
?>
Выводы
Таким образом получился простой и легко встраиваемый календарь событий, который быстро работает и легко настраивается, работающий на чистом PHP+javascript без дополнительных библиотек.
Литература
Автор: bohdaniel