Календарь событий PHP + Javascript

Недавно возникла потребность создать календарь событий, где каждая дата в календаре будет подсвечена ссылкой, если какое-нибудь событие присутствует для каждого числа. Если мне разрешат оставить ссылку, здесь демонстрация работы календаря.

Задача вроде бы не сложная, но среди немногочисленных решений в интернете я не нашел подходящего по следующим причинам: слишком сложный и непонятный код, медленные запросы к БД (это особенно ощущается, если в базе много записей), использование библиотеки jQuery, к которой я отношусь не очень хорошо.

Итак, к плюсам моего календаря можно отнесли следующее:

  1. Весь код помещается в 200 строчек и состоит из одного файла, который подключается через include
  2. Скрипт состоит из чистого php + javascript без использования библиотеки jQuery
  3. Используются простые и оптимизированные запросы к БД
  4. Подгрузка следующего (предыдущего) месяца происходит через 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 без дополнительных библиотек.

Литература


  1. Календарь на PHP
  2. Календари на javascript
  3. Выборка из базы — ответы на Тостере

Похожие публикации

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 10

    +1
    Поздравляю, вы, кажется, во-первых изобрели велосипед, во-вторых, написали ужасный код, который стоит использовать разве только в качестве подборки bad practice'ов. alert() для отображения ошибок, отсутствие шаблонизации, die() в PHP-коде, вот это всё — оно нехорошо.
    • НЛО прилетело и опубликовало эту надпись здесь
        0
        а где SQL-инъекцию нашли? Там есть проверка через regex, и как её обойти я что-то не представляю
        • НЛО прилетело и опубликовало эту надпись здесь
        +2
        Хороший пример того, как не надо писать.
          0
          мои глаза кровоточат
            +15
            Вот поэтому и не любят PHP-шников:( У НЛО, наверное, всё плохо, раз за такие статьи инвайты дают.
            Уважаемый автор. В вас наверное сейчас кипит негодование, ведь вашу статью раскритиковали, и минусов в карму наставили. И вполне логично ожидать, что в хабре вы разочаровались. Поэтому дам вам немного развернутой критики. Возможно вы задумаетесь об архитектуре своего кода и будете писать получше.
            0) Почитайте www.phptherightway.com, там даже на русском есть
            1) Код-стайл. У вас его нет. Где-то скобки на строке с оператором, где-то ниже. Отступы сбиты. Выберите один стиль (рекомендую PSR-2) и исходите из него.
            2) Запросы к базе+PHP+HTML (который к тому же генерируется через echo)+Javascript в одном файле. Это невозможно дебажить и расширять. Если будет хоть какое-то усложнение логики, всё пропало. Проще будет выкинуть и переписать. Старайтесь держать работу с данными отдельно, бизнес-логику отдельно, шаблоны (даже если будете шаблонизировать через PHP (только без echo '<table>', пожалуйста!) — на первое время он неплохо с этим справится) отдельно, Javascript отдельно. По возможности избавляйтесь от onclick в тэгах.
            3) Именование переменных. Дочитав до $d[$i]=$cd, я заплакал. Понять, что это значит, решительно невозможно. В итоге «самое интересное» (по вашим словам) осталось для меня недоступным.
            4) Попробуйте класс DateTime для работы с датами, вам должно понравиться
            5) Используйте подготовленные выражения, прекратите конкатенировать запросы. В данном конкретном примере инъекции не будет, т.к. вы делаете проверку через preg_match, но в жизни бывает всё значительно сложнее. Кстати, у вас ошибка вот тут: «AND enddate>=$fromdate». Не хватает кавычек в запросе. Забавно, что mysql воспримет это без выбрасывания ошибки, т.к. в выражении «AND enddate>=2014-09-01» он вычислит разность и выведет результат.
            6) У вас при запросе скрипта через AJAX возвращается календарь вместе с тэгом script, после чего вы его innerHTML-ите. Когда я это понял, волосы встали дыбом. Это исключительное везение, что в ваших скриптах только функция переопределяется, да изменяется значение глобальных переменных (к счастью, тоже на правильные). Если бы у вас там было навешивание событий, к примеру, были бы дикие глюки.
            Пожалуй хватит для начала. Постарайтесь, пожалуйста, писать чуть более с оглядкой на архитектуру. Вы сами поймёте, как это удобно и хорошо, когда ощутите, насколько меньше багов у вас получается.
              –1
              Большое спасибо за конструктивную критику. Планирую в ближайшее время все исправить и надеюсь, эта статья кому-то поможет, потому что в свое время я не нашел подробной информации как сделать календарь событий. Поэтому и написал эту статью как мог. Вам спасибо, а всем остальным — нет! Потому что не написали почему им не понравилось и что для этого нужно сделать. Написать «фу, какая гадость...» я тоже могу
                +2
                Вы поймите — плохого кода и начинающих программистов так много, что отвечая каждому внятно и конструктивно у опытных программистов просто не останется времени на написание хорошего кода. Вот поэтому у программистов очень ценится самообучение, изучение чужого кода и практик программирования, а так же самостоятельная работа над своими ошибками.

                Статья же может помочь только вам — понять свои ошибки и постараться не допускать их в будущем, новичков, к сожалению, к статье подпускать нельзя — не понимая нюансов они растащат по своим проектам.
              0
              Вот хороший пример реализации календаря в виде модуля Zend2

              https://github.com/SCLInternet/BmCalendar

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое