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

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: , , ,
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