Делаем новостной календарь
Date June 2nd, 2010 Author Vitaly Agapov
— Благодарю за напоминание, Вальтер. Я, представьте, каждый день заглядываю в календарь. По утрам.
— «17 мгновений весны»
Одной из стандартных фич на любом веб-портале является календарь новостей, позволяющий быстро выбрать новости за определённый день. Вот и я заинтересовался этой штукой и разобрался, как её реализовать. Как выяснилось, для этого потребуются лишь небольшие знания JavaScript, а также плагин jQuery-UI для фреймворка jQuery.
Что конкретно мы хотим получить? Мы хотим симпатичный календарик на сайдбаре страницы с новостями. Он должен выделять дни, за которые есть новости и давать пользователю кликать только на них. Он должен подгружать информацию о днях с новостями за каждый новый месяц, открываемый пользователем, а также сохранять в кэше уже подгруженную информацию. Да и хватит, пожалуй… Само собой, клик на дате в календаре должен вызывать переход на страницу с новостями за выбранный день.
Не пускаясь в рассуждения, просто приведу кусок (точнее, два куска) кода. Оба куска будут написаны на Perl’е, но это не играет решающей роли – львиная доля кода представляет собой JavaScript. Так что можно по-быстрому переделать всё под, скажем, PHP.
Первый кусок – это часть Перлового CGI-скрипта, процедура, возвращающая код с календарём для генерируемой страницы:
sub sub_newscalendar { my $o; my @tm=localtime; my $year=$tm[5]+1900; my $month=$tm[4]+1; my $day=$tm[3]; $o.=qq~ <!-- Блочный элемент для вывода аяксовой анимашки на время подгрузки календаря --> <div id="calendar_loading"><img src="/images/loading2.gif" alt=""/></div> <!-- Блочный элемент для самого календаря --> <div id="calendar"></div> <script type="text/javascript"> //<![CDATA[ var cache_days = new Array(); // Установим текущую дату var today = new Date(); var curr_date = today.getFullYear()+'-'+twoDigits(today.getMonth()+1); var is_first = true; var ajax_exec = false; // После загрузки страницы начинаем загрузку календаря \$(document).ready(function(){ loadMonthCalendar($year, $month); }); function showCalendar() { // Функция прячет аяксовую анимашку и показывает div с календарем \$('#calendar_loading').css('display', 'none'); \$('#calendar').css('display', 'block'); } function createCalendar() { // функция, вызывающая конструктор datepicker из jQuery-UI для элемента #calendar \$("#calendar").datepicker({ monthNames: ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'], monthNamesShort: ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'], dayNames: ['Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота'], dayNamesMin: ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'], nextText: 'Вперёд', prevText: 'Назад', firstDay: 1, gotoCurrent: false, hideIfNoPrevNext: true, shortYearCutoff: 20, dateFormat: 'yy-mm-dd', minDate: new Date(2009, 11, 01), maxDate: new Date($year, ($month - 1), $day), // событие, срабатывающее при выборе пользователем другого месяца onChangeMonthYear: function(year, month, inst) { curr_date = year+'-'+twoDigits(month); if (is_first) { return false; } if (ajax_exec) { return false; } if (typeof(cache_days[curr_date]) != 'object') { // Если это не первый вызов функции, не вызов из Ajax // а также если в кэше этого месяца нет, то удаляем календарь и загружаем его заново destroyCalendar(); loadMonthCalendar(year, month); } }, // Событие, срабатывающее при клике на дату onSelect: function(dateText, inst) { year = dateText.substr(0, 4); month = dateText.substr(5, 2); day = dateText.substr(8, 2); if (typeof(cache_days[year+'-'+month]) == 'object') { if (in_array(parseInt(day,10), cache_days[year+'-'+month])) { // Если выбранная дата есть в кэше, то сделать на неё переход location.href = '/info/newsarchive/'+year+'-'+month+'-'+day+'/'; } } return false; }, // Событие вызывается перед отрисовкой каждого дня в месяце beforeShowDay: function(date) { this_day = date.getFullYear()+'-'+twoDigits(date.getMonth() + 1)+'-'+twoDigits(date.getDate()); var arr = new Array(); // Функция должна вернуть массив из трёх элементов arr[0] = false; // Разрешать ли выбор даты arr[1] = ''; // Какой css-класс назначить arr[2] = ''; // Какую всплывающую подсказку показывать is_other = (date.getFullYear()+'-'+twoDigits(date.getMonth() + 1) != curr_date); is_week_end = (date.getDay() == 6 || date.getDay() == 0); is_selected = (this_day == '$year-'+twoDigits($month)+'-'+twoDigits($day)); // Это ключевой момент. Если дата присутствует в кэше, то будет разрешён выбор этой даты is_href = in_array(date.getDate(), cache_days[date.getFullYear()+'-'+twoDigits(date.getMonth() + 1)]); if (is_other) { arr[0] = false; arr[1] = 'other-month'; } else if (is_week_end && is_selected) { arr[0] = is_href; arr[1] = 'selected'; } else if (is_week_end && !is_selected) { arr[0] = is_href; arr[1] = 'week-end'; } else if (!is_week_end && is_selected) { arr[0] = is_href; arr[1] = 'selected'; } else if (!is_week_end && !is_selected) { arr[0] = is_href; arr[1] = 'weekday'; } return arr; } }); } function loadMonthCalendar(year, month) { // Вызываем аяксом скрипт get-calendar, возвращающий нам в виде JSON список дней ajax_exec = true; \$.post( '/get-calendar', { year: year, month: month}, function (data) { // Сохраняем полученные значения в кэш cache_days[data.year+'-'+data.month] = data.days; showCalendar(); createCalendar(); ajax_exec = false; if (is_first) { is_first = false; \$('#calendar').datepicker('setDate', new Date($year, $month - 1, $day)); } else { \$('#calendar').datepicker('setDate', new Date(year, month - 1, 1)); } }, 'json' ); } function destroyCalendar() { // Удаление календаря \$('#calendar').css('display', 'none'); \$('#calendar_loading').css('display', 'block'); \$('#calendar').datepicker('destroy'); } function twoDigits(str) { // Функция возвращает число в виде строки из двух символов if (typeof(str) != 'string') { str = str.toString(); } return str.length < 2 ? '0' + str : str; } function in_array(val, ar) { // Функция проверяет наличие элемента в массиве if (typeof(ar) != 'object' || !ar || !ar.length) { return false; } for (key in ar) { if (val == ar[key]) { return true; } } return false; } //]]> </script> ~; return $o; }
Для полноты картины посмотрим текст скрипта get-calendar. Он должен для конкретного месяца составлять список дней, для которых есть новости, и возвращать их в формате JSON.
#!/usr/bin/perl use CGI; use warnings; use DBI; use JSON; my $cgi=new CGI(); $dbh = DBI->connect("DBI:mysql:database=dbname;host=localhost","username","passwd",{'RaiseError' => 1}); $dbh->do("SET NAMES utf8"); $dbh->do("SET CHARACTER SET utf8"); my %json_hash; $json_hash{month}=sprintf("%02d", $cgi->param('month')); $json_hash{year}=$cgi->param('year'); my @days; unless ($json_hash{month}=~/^\d+$/ && $json_hash{year}=~/^\d+$/) { print qq~ {"days":[],"year":$year,"month":"$month"} ~; exit(0); } # Это SQL-запрос, вытягивающий то, что нам надо my $sql_query="select day(create_date) from news where month(create_date)=".$json_hash{month}." AND year(create_date)=".$json_hash{year}; my $sth=$dbh->prepare($sql_query); $sth->execute; while (my $day=$sth->fetchrow()) { push(@days, $day); } $sth->finish; $json_hash{days}=\@days; my $json = JSON->new->allow_nonref; # С помощью метода encode преобразуем хэш в JSON-текст. my $json_text=$json->encode( \%json_hash ); print qq~ $json_text ~; 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
[…] Рассмотрим простенький пример использования модуля из статьи про календарь: […]