Создание встроенного модуля CMS. Часть 2

Материал из Школьный портал: справочника
Перейти к: навигация, поиск

Внимание! Данная статья предполагает, что вы уже читали первую часть.

Внимание! Модуль уже реализован в виде встроенного и выйдет в следующей версии портала после 5.2.1. Оставляем статью в качестве обучающего материала.

На этот раз сделаем карту сайта на основе главного меню.

Этот пример скорее учебный, в реальной жизни имеет смысл, если главне меню у вас иерархическое и очень развесистое настолько, что охватить его вниманием, просто гуляя по нему наведением мыши, — дело непростое.

Название

Пусть компонент называется "Карта сайта" для людей, а внутреннее имя будет "sitemap".

Регистрация в базе

INSERT INTO MODULES (MODULENAME, VISIBLE_NAME, M_DEF_LINK, ACCESS, M_PLACE)
VALUES ('sitemap', 'Карта сайта', 'mod=sitemap', ',6,,2,,4,', 'center');

Код модуля

sub sitemap()
{
	# Здесь будет всё самое интересное
}

Как хранится главное меню

Раз мы определились, что навигация — это источник данных для карты сайта, первым делом опишу поля таблицы mainmenu из базы CMS.gdb

Таблица mainmenu хранит пункты меню. Одна строка — один пункт.

Описание полей:

(лучше прямо сейчас открывайте таблицу в вашем любимом инструменте и смотрите на то, что CMS положила в таблицу, чтобы представить меню на вашем сайте)

I_ID      Идентификатор элемента меню, автоинкрементный счётчик,
          пользователю не показывается

I_NAME    Название пункта меню, именно его видит пользователь в редакторе и на сайте

I_LINK    URL (если в этом поле пусто, значит это папка)

I_PARENT  Идентификатор папки.
          Если данный элемент лежит в папке, там будет какое-то число, иначе 0.
          Пользователю нигде явно не показывается, но иерархия
          в редакторе и на сайте строится исходя из этого поля.
          У плоского меню (совсем без иерархии) все значения равны 0.

I_LEVEL   Уровень вложенности (вложенность не ограничена, допустимо любое число).
          Минимум 1 — в корне, 2 и выше — в папке.
          Пользователю нигде не показывается.

I_SORT    Порядок сортировки.
          Пользователь перетаскивает элементы мышкой в редакторе,
          а это поле обновляется соответствующим образом автоматически.
          Там просто числа, для SQL-оператора ORDER BY.

PUBLISHED Если там 1, значит пункт меню опубликован, на сайте показывать можно,
          если там 0 — CMS не должна его показывать на сайте.
          Поставленная или снятая галочка в редакторе означает 1 или 0 соответственно.

OWNER     Владелец пункта меню. Если 6, значит школьный сайт. Всё остальное — блоги.


Самый простейший способ показать меню, это сгенерировать список ссылок, причём, иерархию можно показать исходя из значения I_LEVEL, и не писать полноценный обход дерева. Этого достаточно для простого списка, который не раскрывается, не подгружается по кликам на узлах, а просто выводится весь сразу.

Для получения минимальных данных, достаточных для такой карты сайта, хватит запроса:

SELECT i_level, i_link, i_name
FROM mainmenu
WHERE published = 1
AND owner = 6
ORDER BY i_sort

Получается примерно вот такой результат с тремя колонками:

Mainmenu minimal query result.png

В первой числа (уровень вложенности), во второй — URL, в третьей — название пункта меню. Результат содержит опубликованные пункты меню школьного сайта согласно сортировке.

Теперь из этого добра осталось сгенерировать html-вёрстку (подставляя значения полей каждой строки результата прямо во фрагмент html-кода) и возвратить её из процедуры.

Код

Добавление в список встроенных модулей

Открыли common.pl, нашли our @modules = ..., дописали туда sitemap.

Простейшая версия

Пишу процедуру, которая покажет плоскую карту. Зато это уже работающий прототип, пока очень простой и предельно прямой. Этот код дописываем в cms.pl.

sub sitemap()
{
	my $html = '<ul>'; # открываю список
 
	# делаю запрос к базе (до q я сократил слово Query, запрос то есть)
	my $q = sqlpcms('select i_level, i_link, i_name from mainmenu
		where published = 1 and owner = 6 order by i_sort');
 
	# эта страшная конструкция делает простое удобство:
	# строки выборки представляются как ключи хеша %hash
	# классический рецепт для DBI. если сразу сложно понять, копируйте, не думая
	my %hash; $q->bind_columns( \( @hash{ @{$q->{NAME_lc} } } ));
 
	# вынимаю результат по одной строчке
	while ( $q->fetch )
	{
		# дописываю в html-код очередной пункт меню в виде ссылки внутри списка
		$html .= qq[<li><a href="$hash{i_link}">$hash{i_name}</a></li>];
	}
 
	$html .= '</ul>'; # закрываю список
	return $html; # возвращаю полученную html-вёрстку
}

В данный момент вызов модуля уже строит карту сайта.

SiteMap flat demo.png

Правда она пока плоская, но это мы сейчас исправим, чуток усложнив код.

Добавим показ иерархии

sub sitemap()
{
	my $html = '<h1>Карта сайта</h1><ul>';
	my $q = sqlpcms('select i_level, i_link, i_name from mainmenu
		where published = 1 and owner = 6 order by i_sort');
	my %hash; $q->bind_columns( \( @hash{ @{$q->{NAME_lc} } } ));
	while ( $q->fetch )
	{
		# уровень вложенности я использую для задания левого отступа
		# на i_level единиц em, только в три раза больше, тут можно поиграться
		$hash{i_level}--; $hash{i_level} *= 3;
		$html .= qq[
<li style="margin-left: $hash{i_level}em">
	<a href="$hash{i_link}">$hash{i_name}</a>
</li>
];
	}
	$html .= '</ul>';
	return $html;
}

Основные изменения в цикле while. Там я беру уровень (который начинается с единицы), вычитаю единицу, чтобы корневые элементы не были сдвинути вообще никак, то есть на 0em, а остальные на n * 3em, где n — уровень (который 1 и более для вложенных элементов).

Ещё я добавил заголовок, тег H1, в самое начало html-кода перед списком.

Что получилось:

SiteMap structured demo.png

Добавим обработку папок

Для полного счастья не хватает только умения отличить папку от пункта. То есть папку показывать неликабельной (а куда кликать-то? Ссылки же нет у папки, есть только коллекция ссылок внутри неё). А ещё можно картинку показать рядом с папкой, соответствующую, чтобы было ещё нагляднее и даже не требовалось писать никаких подсказок.

sub sitemap()
{
	my $html = '<h1>Карта Сайта</h1>
<style> .sitemap-folder { padding-left: 24px; background: url(/sp/img/folder.gif) 0 50% no-repeat; } </style>
<ul>';
	my $q = sqlpcms('select i_level, i_link, i_name from mainmenu
		where published = 1 and owner = 6 order by i_sort');
	my %hash; $q->bind_columns( \( @hash{ @{$q->{NAME_lc} } } ));
	while ( $q->fetch )
	{
		$hash{i_level}--; $hash{i_level} *= 3;
		if ( $hash{i_link} )
		{
			$html .= qq[
<li style="margin-left: $hash{i_level}em">
	<a href="$hash{i_link}">$hash{i_name}</a>
</li>];
		}
		else
		{
			$html .= qq[
<li class="sitemap-folder" style="margin-left: $hash{i_level}em">
	$hash{i_name}
</li>];
		}
	}
	$html .= '</ul>'; return $html;
}

Изменения: во-первых, я завёл CSS-класс для папок (sitemap-folder), вынес его определение в html-код отдельным тегом. Потому что каждый раз вствялсть такое большое и однообразное в аттрибут style тега li неэкономно. Это тоже не идеальных вариант. В реальной жизни, когда CSS-кода станет много, выдавать его на страницу каждый раз станет невыгодно и лучше вынести его в отдельный файл, чтобы браузер загрузил его один раз, а в дальнейшем брал из кеша. Но сейчас не об этом. Для демонстрации хватит и так.

Во-вторых (вот это уже самое главное), я разветвил тело цикла while в том месте, где генерирую фрагмент HTML-кода с очередным элементом. Если URL не пустой, значит верстаю как пункт меню со ссылкой (как раньше), иначе как папку (уже без ссылки и с описанным выше классом, который лепит картинку слева от названия).

Картинку я взял из комплекта поставки портала только для примера. В реальной жизни лучше не завязываться на подобные внуренние вещи и сослаться на картинку, которая не изменится точно. Например, можете положить произвольную картинку себе на сервер или состаться на другой сервер, где постоянные пути гарантированы.

Окончательный вариант:

SiteMap structured demo with folders.png

Удачи в разработке!