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}
Ещё раз посмотреть на демо можно, напомню, тут.