Делаем новостной календарь

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: , , ,
Category: Web-dev | 8 Comments »

Comments

8 комментариев на “Делаем новостной календарь”

  1. Vadym

    Возникли проблемы с переводом на PHP.

  2. Vitaly Agapov

    Проблемы – это очень неконкретно

  3. Vitaly Agapov

    Пересмотрел код скрипта – были кое-какие проблемы с отображением SyntaxHighlighter’ом. В частности, конструкция && в тексте выглядела как &amp;&amp;. Может быть, проблема в этом.

  4. Vitaly Agapov

    Плюс особенность Перла – в тексте JS все символы $ заэкранированы. В чистом JS и на PHP экранирование надо будет убрать.

  5. Игорь

    Единственный минус, что при просмотре старых новостей, календарь всегда переходит на текущий месяц

  6. Vitaly Agapov

    Есть такое, но при желании и должной сноровке допиливается на раз-два.

  7. Сардор

    при просмотре старых новостей, календарь всегда переходит на текущий месяц. Как можно оставит вқбранная страница

     

  8. agapoff.name | IT блог » Blog Archive » Perl: работаем с XML и JSON

    […] Рассмотрим простенький пример использования модуля из статьи про календарь: […]

Leave a comment

 Comment Form