Делаем новостной календарь
Date June 2nd, 2010 Author Vitaly Agapov
— Благодарю за напоминание, Вальтер. Я, представьте, каждый день заглядываю в календарь. По утрам.
— «17 мгновений весны»
Одной из стандартных фич на любом веб-портале является календарь новостей, позволяющий быстро выбрать новости за определённый день. Вот и я заинтересовался этой штукой и разобрался, как её реализовать. Как выяснилось, для этого потребуются лишь небольшие знания JavaScript, а также плагин jQuery-UI для фреймворка jQuery.
Что конкретно мы хотим получить? Мы хотим симпатичный календарик на сайдбаре страницы с новостями. Он должен выделять дни, за которые есть новости и давать пользователю кликать только на них. Он должен подгружать информацию о днях с новостями за каждый новый месяц, открываемый пользователем, а также сохранять в кэше уже подгруженную информацию. Да и хватит, пожалуй… Само собой, клик на дате в календаре должен вызывать переход на страницу с новостями за выбранный день.
Не пускаясь в рассуждения, просто приведу кусок (точнее, два куска) кода. Оба куска будут написаны на Perl’е, но это не играет решающей роли – львиная доля кода представляет собой JavaScript. Так что можно по-быстрому переделать всё под, скажем, PHP.
Первый кусок – это часть Перлового CGI-скрипта, процедура, возвращающая код с календарём для генерируемой страницы:
001.
sub
sub_newscalendar
002.
{
003.
my
$o
;
004.
my
@tm
=
localtime
;
005.
my
$year
=
$tm
[5]+1900;
006.
my
$month
=
$tm
[4]+1;
007.
my
$day
=
$tm
[3];
008.
$o
.=qq~
009.
<!-- Блочный элемент для вывода аяксовой анимашки на время подгрузки календаря -->
010.
<div id=
"calendar_loading"
><img src=
"/images/loading2.gif"
alt=
""
/></div>
011.
<!-- Блочный элемент для самого календаря -->
012.
<div id=
"calendar"
></div>
013.
<script type=
"text/javascript"
>
014.
//<![CDATA[
015.
var cache_days = new Array();
016.
// Установим текущую дату
017.
var today = new Date();
018.
var curr_date = today.getFullYear()+
'-'
+twoDigits(today.getMonth()+1);
019.
var is_first = true;
020.
var ajax_exec = false;
021.
// После загрузки страницы начинаем загрузку календаря
022.
\$(document).ready(function(){
023.
loadMonthCalendar(
$year
,
$month
);
024.
});
025.
function showCalendar()
026.
{ // Функция прячет аяксовую анимашку и показывает div с календарем
027.
\$(
'#calendar_loading'
).css(
'display'
,
'none'
);
028.
\$(
'#calendar'
).css(
'display'
,
'block'
);
029.
}
030.
function createCalendar()
031.
{ // функция, вызывающая конструктор datepicker из jQuery-UI для элемента
#calendar
032.
\$(
"#calendar"
).datepicker({
033.
monthNames: [
'Январь'
,
'Февраль'
,
'Март'
,
'Апрель'
,
'Май'
,
'Июнь'
,
'Июль'
,
'Август'
,
'Сентябрь'
,
'Октябрь'
,
'Ноябрь'
,
'Декабрь'
],
034.
monthNamesShort: [
'Янв'
,
'Фев'
,
'Мар'
,
'Апр'
,
'Май'
,
'Июн'
,
'Июл'
,
'Авг'
,
'Сен'
,
'Окт'
,
'Ноя'
,
'Дек'
],
035.
dayNames: [
'Воскресенье'
,
'Понедельник'
,
'Вторник'
,
'Среда'
,
'Четверг'
,
'Пятница'
,
'Суббота'
],
036.
dayNamesMin: [
'Вс'
,
'Пн'
,
'Вт'
,
'Ср'
,
'Чт'
,
'Пт'
,
'Сб'
],
037.
nextText:
'Вперёд'
,
038.
prevText:
'Назад'
,
039.
firstDay: 1,
040.
gotoCurrent: false,
041.
hideIfNoPrevNext: true,
042.
shortYearCutoff: 20,
043.
dateFormat:
'yy-mm-dd'
,
044.
minDate: new Date(2009, 11, 01),
045.
maxDate: new Date(
$year
, (
$month
- 1),
$day
),
046.
047.
// событие, срабатывающее при выборе пользователем другого месяца
048.
onChangeMonthYear: function(year, month, inst) {
049.
curr_date = year+
'-'
+twoDigits(month);
050.
if
(is_first) {
051.
return
false;
052.
}
053.
if
(ajax_exec) {
054.
return
false;
055.
}
056.
if
(typeof(cache_days[curr_date]) !=
'object'
) {
057.
// Если это не первый вызов функции, не вызов из Ajax
058.
// а также если в кэше этого месяца нет, то удаляем календарь и загружаем его заново
059.
destroyCalendar();
060.
loadMonthCalendar(year, month);
061.
}
062.
},
063.
064.
// Событие, срабатывающее при клике на дату
065.
onSelect: function(dateText, inst)
066.
{
067.
year = dateText.
substr
(0, 4);
068.
month = dateText.
substr
(5, 2);
069.
day = dateText.
substr
(8, 2);
070.
if
(typeof(cache_days[year+
'-'
+month]) ==
'object'
) {
071.
if
(in_array(parseInt(day,10), cache_days[year+
'-'
+month])) {
072.
// Если выбранная дата есть в кэше, то сделать на неё переход
073.
location.href =
'/info/newsarchive/'
+year+
'-'
+month+
'-'
+day+
'/'
;
074.
}
075.
}
076.
return
false;
077.
},
078.
079.
// Событие вызывается перед отрисовкой каждого дня в месяце
080.
beforeShowDay: function(date)
081.
{
082.
this_day = date.getFullYear()+
'-'
+twoDigits(date.getMonth() + 1)+
'-'
+twoDigits(date.getDate());
083.
var arr = new Array(); // Функция должна вернуть массив из трёх элементов
084.
arr[0] = false; // Разрешать ли выбор даты
085.
arr[1] =
''
; // Какой css-класс назначить
086.
arr[2] =
''
; // Какую всплывающую подсказку показывать
087.
is_other = (date.getFullYear()+
'-'
+twoDigits(date.getMonth() + 1) != curr_date);
088.
is_week_end = (date.getDay() == 6 || date.getDay() == 0);
089.
is_selected = (this_day ==
'$year-'
+twoDigits(
$month
)+
'-'
+twoDigits(
$day
));
090.
// Это ключевой момент. Если дата присутствует в кэше, то будет разрешён выбор этой даты
091.
is_href = in_array(date.getDate(), cache_days[date.getFullYear()+
'-'
+twoDigits(date.getMonth() + 1)]);
092.
093.
if
(is_other) {
094.
arr[0] = false;
095.
arr[1] =
'other-month'
;
096.
}
else
if
(is_week_end && is_selected) {
097.
arr[0] = is_href;
098.
arr[1] =
'selected'
;
099.
}
else
if
(is_week_end && !is_selected) {
100.
arr[0] = is_href;
101.
arr[1] =
'week-end'
;
102.
}
else
if
(!is_week_end && is_selected) {
103.
arr[0] = is_href;
104.
arr[1] =
'selected'
;
105.
}
else
if
(!is_week_end && !is_selected) {
106.
arr[0] = is_href;
107.
arr[1] =
'weekday'
;
108.
}
109.
return
arr;
110.
}
111.
});
112.
}
113.
114.
function loadMonthCalendar(year, month)
115.
{ // Вызываем аяксом скрипт get-calendar, возвращающий нам в виде JSON список дней
116.
ajax_exec = true;
117.
\$.post(
118.
'/get-calendar'
,
119.
{ year: year, month: month},
120.
function (data)
121.
{
122.
// Сохраняем полученные значения в кэш
123.
cache_days[data.year+
'-'
+data.month] = data.days;
124.
showCalendar();
125.
createCalendar();
126.
ajax_exec = false;
127.
if
(is_first) {
128.
is_first = false;
129.
\$(
'#calendar'
).datepicker(
'setDate'
, new Date(
$year
,
$month
- 1,
$day
));
130.
}
else
{
131.
\$(
'#calendar'
).datepicker(
'setDate'
, new Date(year, month - 1, 1));
132.
}
133.
},
134.
'json'
135.
);
136.
}
137.
138.
function destroyCalendar()
139.
{ // Удаление календаря
140.
\$(
'#calendar'
).css(
'display'
,
'none'
);
141.
\$(
'#calendar_loading'
).css(
'display'
,
'block'
);
142.
\$(
'#calendar'
).datepicker(
'destroy'
);
143.
}
144.
function twoDigits(str)
145.
{ // Функция возвращает число в виде строки из двух символов
146.
if
(typeof(str) !=
'string'
) {
147.
str = str.toString();
148.
}
149.
return
str.
length
< 2 ?
'0'
+ str : str;
150.
}
151.
function in_array(val, ar)
152.
{ // Функция проверяет наличие элемента в массиве
153.
if
(typeof(ar) !=
'object'
|| !ar || !ar.
length
) {
154.
return
false;
155.
}
156.
157.
for
(key in ar) {
158.
if
(val == ar[key]) {
159.
return
true;
160.
}
161.
}
162.
return
false;
163.
}
164.
//]]>
165.
</script>
166.
~;
167.
return
$o
;
168.
}
Для полноты картины посмотрим текст скрипта get-calendar. Он должен для конкретного месяца составлять список дней, для которых есть новости, и возвращать их в формате JSON.
01.
#!/usr/bin/perl
02.
use
CGI;
03.
use
warnings;
04.
use
DBI;
05.
use
JSON;
06.
my
$cgi
=new CGI();
07.
08.
$dbh
= DBI->
connect
(
"DBI:mysql:database=dbname;host=localhost"
,
"username"
,
"passwd"
,{
'RaiseError'
=> 1});
09.
10.
$dbh
->
do
(
"SET NAMES utf8"
);
11.
$dbh
->
do
(
"SET CHARACTER SET utf8"
);
12.
13.
my
%json_hash
;
14.
$json_hash
{month}=
sprintf
(
"%02d"
,
$cgi
->param(
'month'
));
15.
$json_hash
{year}=
$cgi
->param(
'year'
);
16.
my
@days
;
17.
unless
(
$json_hash
{month}=~/^\d+$/ &&
$json_hash
{year}=~/^\d+$/)
18.
{
19.
print
qq~
20.
{
"days"
:[],
"year"
:
$year
,
"month"
:
"$month"
}
21.
~;
22.
exit
(0);
23.
}
24.
# Это SQL-запрос, вытягивающий то, что нам надо
25.
my
$sql_query
=
"select day(create_date) from news where month(create_date)="
.
$json_hash
{month}.
" AND year(create_date)="
.
$json_hash
{year};
26.
my
$sth
=
$dbh
->prepare(
$sql_query
);
27.
$sth
->execute;
28.
while
(
my
$day
=
$sth
->fetchrow())
29.
{
30.
push
(
@days
,
$day
);
31.
}
32.
$sth
->finish;
33.
$json_hash
{days}=\
@days
;
34.
my
$json
= JSON->new->allow_nonref;
35.
36.
# С помощью метода encode преобразуем хэш в JSON-текст.
37.
my
$json_text
=
$json
->encode( \
%json_hash
);
38.
39.
print
qq~
40.
$json_text
41.
~;
42.
exit
(0);
На этом всё.
Рабочий вариант можно посмотреть здесь.
Tags: Ajax, jQuery, Perl, Web-dev
Category:
Web-dev |
8 Comments »
8 October 2010 - 17:42
Возникли проблемы с переводом на PHP.
11 October 2010 - 8:05
Проблемы – это очень неконкретно
11 October 2010 - 8:10
Пересмотрел код скрипта – были кое-какие проблемы с отображением SyntaxHighlighter’ом. В частности, конструкция && в тексте выглядела как &&. Может быть, проблема в этом.
18 October 2010 - 15:06
Плюс особенность Перла – в тексте JS все символы $ заэкранированы. В чистом JS и на PHP экранирование надо будет убрать.
24 March 2011 - 0:53
Единственный минус, что при просмотре старых новостей, календарь всегда переходит на текущий месяц
25 March 2011 - 10:04
Есть такое, но при желании и должной сноровке допиливается на раз-два.
19 September 2014 - 8:02
при просмотре старых новостей, календарь всегда переходит на текущий месяц. Как можно оставит вқбранная страница
22 September 2015 - 16:33
[…] Рассмотрим простенький пример использования модуля из статьи про календарь: […]