AJAX + jQuery + Solspace Mini Calendar + ExpressionEngine

Для одного из проектов мне нужно было сделать небольшой календарик, который позволял бы, во-первых, перелистывать месяцы без перезагрузки страниц, а во-вторых, при наведении на дату, содержащую какое-либо событие, красиво отображать его (события) название и время проведения. Проект, для которого нужен был этот календарь, сделан на ExpressionEngine, а сам календарик был сделан с помощью модуля Solspace Calendar.

Поскольку я мастер творческого копи-паста, а также стойкий сторонник теории, что всё уже придумано до меня, я отправилась бороздить просторы форума Solspace в поисках готового решения данной задачи. К своему удивлению, обнаружила всего 2 поста с впоросами на данную тему, но ни одного кусочка кода, который можно было бы использовать.

Google радушно предложил ссылку на двухлетней давности запись в блоге некоего Kyle Truscott, который решал сходную задачу с помощью встроенного календаря EE. На основе его кода я сделала свой для календаря, сделанного на Solspace Calendar.

Вот что получилось в итоге:

Посмотреть календарь в действии можно тут

Шаг 1. Создаём мини-календарь

Создаём шаблон, который будет отвечать за отображение календаря. У меня он называется inc/mini. Для мини-календаря нам придётся использовать advanced код, который приводится в документации к Solspace Calendar, но с небольшими модификациями:

{exp:calendar:cal date_range_start="<?php echo $year; ?>-<?php echo $month; ?>-01" date_range_end="<?php echo $year; ?>-<?php echo $month; ?>-last" dynamic="off"}
<div id="mc_calendar">
  {display_each_month}
    <table>
      <thead>
        <tr>
          <th colspan="1"><a id="mc_prev_month" class="icon left" href="#" alt="{prev_month format="%m"}" title="{prev_month format="%Y"}">«</a></th>
          <th colspan="5"><span class="monthTitle">{month format="%F %Y"}</span></th>
                  <th colspan="1"><a id="mc_next_month" class="icon right" href="#" alt="{next_month format="%m"}" title="{next_month format="%Y"}"> »</a></th>
               </tr>
               <tr id="mc_days">
                  {display_each_day_of_week}
                  <th class="{if day_of_week_is_weekend}weekend{/if} {if day_of_week_is_current}current{/if}">{day_of_week_short}</th>
                  {/display_each_day_of_week}
               </tr>
            </thead>
            <tbody>
               {display_each_week}
               <tr>
                  {display_each_day}
                  <td class="
                     {if !day_in_current_month == FALSE}mc_pad{/if}
                     {if day_event_total > 0}has_events{/if}
                     {if count == 7}selected{/if}
                     {if day_is_today}today{/if}
                  ">
                  {if day_in_current_month}<div class="mc_date">
                     {if day_event_total}<span class="day_link" title="http://domain.ru/template_group/template/{day format="%Y/%m/%d"}">{/if}
                     {day}
                     {if day_event_total}</span>{/if}</div>
                  {/if}
                  {if day_in_next_month}<div class="mc_date_next">
                        <strong class="middot">·</strong>
                     </div>{/if}</td>
                  {/display_each_day}
               </tr>
               {/display_each_week}
            </tbody>
         </table>
         {/display_each_month}
      </div>
   {/exp:calendar:cal}

В общем и целом, это стандартный код для мини-календаря Solspace Calendar за некоторыми исключениями:

1. В открывающем теге календаря для указания первого и последнего дня генерируемого месяца используем параметры, которые позднее будем передавать с помощью джаваскрипта:

{exp:calendar:cal date_range_start="<?php echo $year; ?>-<?php echo $month; ?>-01" date_range_end="<?php echo $year; ?>-<?php echo $month; ?>-last" dynamic="off"}

2. Это часть, отвечающая за формирование ссылок на следующий и предыдущий месяцы:

 <tr>
 <th colspan="1"><a id="mc_prev_month" class="icon left" href="#" alt="{prev_month format="%m"}" title="{prev_month format="%Y"}">«</a></th>
 <th colspan="5"><span class="monthTitle">{month format="%F %Y"}</span></th>
 <th colspan="1"><a id="mc_next_month" class="icon right" href="#" alt="{next_month format="%m"}" title="{next_month format="%Y"}"> »</a></th>
</tr>

Поскольку мы будем обновлять календарь автоматически, то атрибут href тэга a мы оставляем пустым, формированием ссылки в каждом конкретном случае займётся jQuery. Чтобы jQuery сумел (сумела? сумело?) понять, какой месяц для текущего - предыдущий, а какой - следующий, мы должны ему (ей?) помочь и прописать соответствующую информацию в атрибутах alt и title.

Для предыдущего месяца:

alt="{prev_month format="%m"}" title="{prev_month format="%Y"}"

Для следующего месяца:

alt="{next_month format="%m"}" title="{next_month format="%Y"}"

Всё это - стандартные теги календаря: prev_month и next_month

3. Также сразу заложим в наш календарь основу для отображения названия события в красивом всплывающем тултипе (как это сказать по-русски?):

{if day_event_total}
  <span class="day_link" title="http://domain.ru/template_group/template/{day format="%Y/%m/%d"}">
{/if}
{day}
{if day_event_total}
  </span>
{/if}

Здесь template_group/template - шаблон, который будет содержать код отображения события. Неважно, где в структуре шаблонов он находится, поскольку он будет вызываться из нашего календаря.

Шаг 2. Создаём шаблон отображения события

Здесь всё очень просто:

{exp:calendar:cal pad_short_weeks="n" date_range_start="{segment_3}-{segment_4}-{segment_5}" date_range_end="{segment_3}-{segment_4}-{segment_5}" dynamic="off"}

   {display_each_day}
   {events}

<strong>"{event_title}"</strong><br />{event_location}, {event_start_date format="%H:%i"}

      {/events}
   {/display_each_day}
{/exp:calendar:cal}

Всё то, что находится между тегами {events}{/events}, содержит код для отображения события. Здесь нужно стремиться к простоте, поскольку отображаться эта информация будет во всплывающих подсказках :)

Шаг 3. Создаём основной шаблон

Вот тут самое интересное :)

Во-первых, конечно, не забываем включить в шаблон вызов jQuery, а также плагина qTip, отвечающего за отображение всплывающих подсказок:

<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript" src="/js/plugins/jquery.qtip.min.js"></script>

Во-вторых, в том месте, где будет отображаться наш календарь, создаём пустой контейнер. У меня это <section> с id=”mc_wrap”:

<section id="mc_wrap">
</section>

Шаг 4. Создаём скрипты

В основном шаблоне, который будет вызывать шаблон с календарём, вставляем в начале небольшой php скрипт для опеределения текущего месяца и года (не забываем включить в этом шаблоне поддержку php on input):

<?php
    $month = date('m');
    $year = date('Y');
?>

В этом же шаблоне перед закрывающим <body> вставляем скрипт, который будет отправлять шаблону с мини-календарём нужный месяц и год. При первом открытии страницы будет отображаться текущий месяц и год (на момент написания этой записи - апрель 2012):

$(document).ready(function(){

    var month = "<?php echo $month; ?>";
    var year = "<?php echo $year; ?>";

    render_calendar(month,year);

    function render_calendar(month,year) {
        $.post(
            "http://domain.ru/index/inc/mini",
            { month : month, year : year },
            function(str) {
                $('#mc_wrap').html(str).fadeIn();
            }
        );
    }

$("#mc_calendar a").live("click", function(){
      $('#mc_calendar').fadeOut('fast');
       month = $(this).attr('alt');
       year = $(this).attr('title');
       render_calendar(month,year);
    });

   
});

Здесь #mc_calendar - это id div’a, в который завёрнут календарь в шаблоне inc/mini, а #mc_wrap - id пустого контейнера, созданного нами в основном шаблоне.

Особенно внимательно нужно отнестись к указанию правильного урла к шаблону с календарём. Я использую NSM .htaccess generator, поэтому в моём случае урл данного шаблона, который я набираю в браузере, выглядит как http://domain.ru/inc/mini. Но для корректной передачи POST-параметров необходимо указать полный урл к шаблону с использованием index.php или, если вы переименовывали этот файл, с соответствующим именем. Так что в моём случае получилось так (я переименовала index.php в index): http://domain.ru/index/inc/mini

Также добавляем скрипт, который будет вызывать шаблон события и отображать его во всплывающей подсказке, и размещаем этот скрипт в шаблоне с мини-календарём:

$(".day_link").qtip({

  content: {
   text: 'Загрузка...'
},
style: { 
      width: 200,
      padding: 5,
       background: '#ba092a',
      color: '#e8ddcb',
      textAlign: 'center',
      border: {
         width: 7,
         radius: 5,
         color: '#ba092a'
      },
      tip: 'bottomLeft',
      name: 'dark' // Inherit the rest of the attributes from the preset dark style
   },
   position: {
      corner: {
         target: 'topRight',
         tooltip: 'bottomLeft'
      }
   },
api: {
   beforeShow: function() {
      var url = this.elements.target.attr('title');
      if (url != '') {
         this.loadContent(url);
      }
   },
   onContentLoad: function() {
      this.elements.target.attr('title', '');
   }
}

});

Здесь .day_link - это класс ячейки календаря, в которой присутствует какое-либо событие. А содержимое шаблона, отображающего это событие, мы подтягиваем с помощью API плагина - для этого мы используем атрибут title тега , который мы указали в коде календаря:

{if day_event_total}
  <span class="day_link" title="http://domain.ru/template_group/template/{day format="%Y/%m/%d"}">
{/if}

Ну а чтобы шаблон календаря понял, какой именно месяц нужно отображать, вставляем в начало этого шаблона php-скрипт, который выцепляет нужное из POST-запроса (также не забываем включить у этого шаблона поддержку php on input):

<?php
if (isset($_POST['month'])) :
    $month = $_POST['month'];
    $year = $_POST['year'];
else :
    $month = date('m');
    $year = date('Y');
endif;
?>

Если в POST не передаётся никакой конкретный месяц и год (то есть в том случае, если посетитель впервые открыл данную страницу), используется текущий месяц и год.

В итоге у нас получаются следующие шаблоны:

Основной шаблон

<?php
    $month = date('m');
    $year = date('Y');
?>

<!doctype html>
<html>
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
 <script type="text/javascript" src="/js/plugins/jquery.qtip.min.js"></script>
</head>
<body>
 <article id="container">
  <section id="mc_wrap">
  </section>
 </article>


<script type="text/javascript">
$(document).ready(function(){

    var month = "<?php echo $month; ?>";
    var year = "<?php echo $year; ?>";

    render_calendar(month,year);

    function render_calendar(month,year) {
        $.post(
            "http://domain.ru/index/inc/mini",
            { month : month, year : year },
            function(str) {
                $('#mc_wrap').html(str).fadeIn();
            }
        );
    }

$("#mc_calendar a").live("click", function(){
      $('#mc_calendar').fadeOut('fast');
       month = $(this).attr('alt');
       year = $(this).attr('title');
       render_calendar(month,year);
    });

   
});
</script>
</body>
</html>

Шаблон с календарём

<?php

if (isset($_POST['month'])) :
    $month = $_POST['month'];
    $year = $_POST['year'];
else :
    $month = date('m');
    $year = date('Y');
endif;
?>
  
{exp:calendar:cal date_range_start="<?php echo $year; ?>-<?php echo $month; ?>-01" date_range_end="<?php echo $year; ?>-<?php echo $month; ?>-last" dynamic="off"}
   
<div id="mc_calendar">
 {display_each_month}
  <table>
    <thead>
      <tr>
         <th colspan="1"><a id="mc_prev_month" class="icon left" href="#" alt="{prev_month format="%m"}" title="{prev_month format="%Y"}">«</a></th>
         <th colspan="5"><span class="monthTitle">{month format="%F %Y"}</span></th>
         <th colspan="1"><a id="mc_next_month" class="icon right" href="#" alt="{next_month format="%m"}" title="{next_month format="%Y"}"> »</a></th>
       </tr>
       <tr id="mc_days">
        {display_each_day_of_week}
         <th class="{if day_of_week_is_weekend}weekend{/if} {if day_of_week_is_current}current{/if}">{day_of_week_short}
         </th>
        {/display_each_day_of_week}
        </tr>
       </thead>
      <tbody>
               {display_each_week}
               <tr>
                  {display_each_day}
                  <td class="
                     {if !day_in_current_month == FALSE}mc_pad{/if}
                     {if day_event_total > 0}has_events{/if}
                     {if count == 7}selected{/if}
                     {if day_is_today}today{/if}
                  ">
                  {if day_in_current_month}<div class="mc_date">
                     {if day_event_total}<span class="day_link" title="http://domain.ru/template_group/template/{day format="%Y/%m/%d"}">{/if}
                     {day}
                     {if day_event_total}</span>{/if}</div>
                  {/if}
                  {if day_in_next_month}<div class="mc_date_next">
                        <strong class="middot">·</strong>
                     </div>{/if}</td>
                  {/display_each_day}
               </tr>
               {/display_each_week}
            </tbody>
         </table>
         {/display_each_month}
      </div>
   {/exp:calendar:cal}
   
<script type="text/javascript">
$(".day_link").qtip({

  content: {
   text: 'Загрузка...'
},
style: { 
      width: 200,
      padding: 5,
       background: '#ba092a',
      color: '#e8ddcb',
      textAlign: 'center',
      border: {
         width: 7,
         radius: 5,
         color: '#ba092a'
      },
      tip: 'bottomLeft',
      name: 'dark' // Inherit the rest of the attributes from the preset dark style
   },
   position: {
      corner: {
         target: 'topRight',
         tooltip: 'bottomLeft'
      }
   },
api: {
   beforeShow: function() {
      var url = this.elements.target.attr('title');
      if (url != '') {
         this.loadContent(url);
      }
   },
   onContentLoad: function() {
      this.elements.target.attr('title', '');
   }
}

});


Шаблон отображения события

{exp:calendar:cal pad_short_weeks="n" date_range_start="{segment_3}-{segment_4}-{segment_5}" date_range_end="{segment_3}-{segment_4}-{segment_5}" dynamic="off"}
 {display_each_day}
  {events}
   <strong>"{event_title}"</strong><br />{event_location}, {event_start_date format="%H:%i"}
  {/events}
 {/display_each_day}
{/exp:calendar:cal}

Ещё раз посмотреть на демо можно, напомню, тут.