2. Пример сайта
Давайте создадим простой веб-сайт и посмотрим, каким образом с помощью Парсера решаются некоторые распространенные задачи. В частности, мы научимся формировать меню разделов, средства альтернативной навигации и списки новостей. Начнем, как водится, с технического задания. Затем наметим общую схему решения поставленной задачи. После этого создадим файлы данных и шаблоны страниц, подробно рассмотрим их устройство, укажем на некоторые недостатки предложенного решения. В конце главы приводится список упражнений и даются ненавязчивые рекомендации по их выполнению.
Замечание. Перед тем как погрузиться в проектирование и создание учебного сайта, имеет смысл прочитать описания операторов, которые мы будем использовать, а именно: context, eval, form, if, item, load, locate, macro, menu, truncate, var.
Сайт состоит из четырех разделов: Новости, Статьи, Ссылки и Контакты. Набор разделов может меняться, поэтому сайт должен быть сделан таким образом, чтобы добавление, удаление или изменение раздела требовало как можно меньше ручной правки.
О каждом разделе известна следующая информация:
- заголовок;
- имя HTML-файла.
Каждый раздел представляет собой отдельную страницу. Все разделы устроены единообразно и состоят из следующих функциональных частей:
- меню;
- заголовок раздела;
- информационное наполнение раздела;
- средства альтернативной навигации.
Меню содержит ссылки на все разделы сайта. Название самого раздела тоже присутствует в меню, но ссылка на нем не ставится. Меню оформлено в виде таблицы, состоящей из одной строки. Каждая ссылка находится в отдельной ячейке таблицы. Названия разделов в ячейках центрированы по горизонтали. Из эстетических соображений ячейки должны быть одинаковой длины.
Заголовок раздела представляет собой строку, заключенную в теги <H1></H1>.
Функциональность и оформление информационного наполнения раздела зависит от того, что содержательно представляет собой этот раздел. Например, в разделе Новости это список новостей, оформленный определенным образом (см. ниже).
Средства альтернативной навигации обеспечивают посетителю возможность последовательно перелистывать все разделы сайта от первого до последнего. В каждом разделе средства альтернативной навигации состоят из двух ссылок: на предыдущий и на следующий раздел. Исключениями из этого правила являются первый и последний разделы (в них нет ссылок на предыдущий и следующий раздел соответственно). Ссылка на предыдущий раздел выглядит так: <<<<, а на следующий >>>>. От остального пространства страницы средства альтернативной навигации отделены двумя горизонтальными линиями (<HR>).
Внимательный читатель обратил внимание, что мы до сих пор ни слова не сказали о взаиморасположении перечисленных выше конструктивных элементов на странице. Это не случайно. Поскольку Парсер позволяет задать типовую структуру страниц, вопрос о том, как именно они будут устроены, перестает быть ключевым для HTML-кодера. Сколько бы ни было разделов, мы сможем изменить их устройство, внеся правку в один единственный файл. Пока сделаем так: сверху поместим меню, под ним заголовок, затем информационное наполнение раздела, а внизу средства альтернативной навигации.
Раздел Новости содержит ссылки на все новости, опубликованные на сайте.
О каждой новости известна следующая информация:
Ссылка ставится на заголовке новости. Справа от ссылки ставится дата новости, выделенная курсивом. Ниже со сдвигом вправо отображается начало текста новости (возьмем первые 170 символов). Добавление, удаление, изменение новостей должно обходиться как можно дешевле и требовать минимума исправлений в шаблонах страниц. По ссылке из раздела новостей посетитель может перейти к соответствующей новости. Страница новости устроена очень просто: сверху заголовок, на следующей строчке дата, а дальше основной текст.
Проектирование остальных трех разделов предоставляется читателю в качестве упражнения.
Всю информацию о разделах будем хранить в файле формата tab-delimited, назовем его _sections.cfg. При формировании HTML-кода разделов будем пользоваться этой информацией, загружая ее в таблицу. Каждому разделу присвоим уникальный числовой идентификатор, который тоже будет храниться в файле _sections.cfg. Раздел всегда можно будет опознать по этому номеру, что избавит нас от ручной правки шаблонов в случае изменения заголовков или переименования файлов.
В файле _sections.cfg предусматриваются следующие столбцы:
- no уникальный числовой идентификатор раздела;
- title заголовок раздела;
- file имя файла раздела.
Одинаковую для всех разделов структуру страницы опишем в виде макроса в файле _macro.cfg. Этот макрос назовем section_html. Его аргументы идентификатор раздела section_no и код его информационного наполнения section_body.
Для каждого раздела создадим отдельный шаблон, в частности, для раздела новостей создадим шаблон news.html. В каждом шаблоне раздела опишем макрос main. Этот макрос будет формировать код информационного наполнения раздела и вызывать макрос section_html, передавая ему номер раздела, который заведомо известен, и сформированный код.
Первым делом макрос section_html с помощью операторов context и load назначает текущую таблицу и загружает в нее данные из файла _sections.cfg.
Затем он конструирует меню разделов, проходя с помощью оператора menu по всей таблице. При этом для того чтобы понять, когда нужно ставить ссылку, а когда нет, номер раздела в текущей строке таблицы (значение в столбце no) сравнивается с номером раздела, переданным в аргументе section_no. Если равенство соблюдается, значит, оператор menu наступил на строку, соответствующую формируемому разделу, и ссылку ставить не нужно. Во всех остальных случаях ссылка ставится.
Однако мы не учли одно из требований технического задания: ячейки HTML-таблицы должны быть одинаковой ширины. Чтобы добиться этого, укажем в каждой ячейке HTML-таблицы ее ширину в процентах от ширины окна. Это значение вычислим так:
|
^eval[round(^item[count]/100)]
|
|
|
|
Конечно, эти вычисления выполняются до вызова оператора menu, но здесь мы пишем о них после, чтобы не заострять внимание на второстепенном.
Сформировав меню, нужно отобразить заголовок раздела. Это не так просто, поскольку макросу передается не заголовок раздела, а его идентификатор. Поэтому заголовок придется найти в таблице по столбцу no:
После заголовка оператор вставляет информационное наполнение раздела, которое получает на халяву при вызове это значение аргумента section_body.
Теперь остается только сформировать ссылки на предыдущий и следующий разделы. Воспользуемся тем, что после вызова оператора locate текущей в таблице является строка, соответствующая формируемому разделу. Следовательно, вызвав оператор item с аргументом next или prev, мы можем получить имя файла следующего или предыдущего раздела соответственно. Кстати, вы не забыли, нужно ведь еще проверить, не является ли раздел первым или последним? Для этого снова используем оператор item с аргументом count (чтобы узнать номер последнего раздела, считая с 1) и к оператору item с аргументом number (чтобы узнать номер формируемого раздела). Если номер раздела равен 1, то мы имеем дело с первым разделом и не ставим ссылку на предыдущий, а если количеству разделов то с последним и не ставим ссылку на следующий. Так, для формирования ссылки на предыдущий раздел имеем:
|
^if[^item[number]==1; <^;<^;<^;<^;; <A HREF="^item[prev;file]"><^;<^;<^;<^;</A> ]
|
|
|
|
Новости также поместим в файл формата tab-delimited, который будет называться _news.cfg. В этом файле будут следующие столбцы:
- no уникальный числовой идентификатор новости;
- title заголовок новости;
- date дата новости;
- text текст новости.
Поскольку новости обновляются часто, мы не станем создавать для каждой из них отдельный шаблон. Лучше подготовим единый шаблон newsdetails.html, принимающий в качестве параметра идентификатор той новости, которую требуется отобразить в данный момент. В разделе новостей на этот шаблон будет стоять несколько ссылок с разными параметрами:
|
<A HREF="newsdetails.html?n_no=1">Москву атакуют гигантские кролики</A> <A HREF="newsdetails.html?n_no=2">С мая введут акцизы на интернет</A> ...
|
|
|
|
Для каждой новости своя ссылка. Кстати, эти ссылки и есть информационное наполнение раздела, которое макрос main из шаблона news.html передает макросу section_html в качестве значения аргумента section_body. Шаблон newsdetails.html будет загружать все новости в таблицу и находить в ней новость с переданным идентификатором:
|
^context[news; ^load[named;news.cfg] ^locate[no;^form[n_no]] ]
|
|
|
|
В корневом каталоге сайта создадим файлы _sections.cfg и _news.cfg. В первой строке каждого из этих файлов укажем названия свойств разделов (или новостей).
|
|
|
|
no > title > file 1 > Новости > news.html 2 > Статьи > articles.html 3 > Ссылки > links.html 4 > Контакты > address.html
|
|
|
|
|
|
|
|
no > title > date > text 1 > Москву атакуют гигантские кролики > 05.03.2001 > Полчища... 2 > С мая вводятся акцизы на интернет > 04.03.2001 > Каждый... 3 > Букву ё скоро отменят > 03.03.2001 > Межведомств...
|
|
|
|
Создадим файл _macro.cfg и опишем в нем макрос section_html.
|
|
|
|
@section_html[section_no;section_body] код раздела <HTML> <HEAD><TITLE>Учебный сайт</TITLE></HEAD> <BODY> # Назначим таблицу sections текущей ^context[sections; # Загрузим в таблицу данные о разделах ^load[named;sections.cfg] # # Сформируем меню разделов # <TABLE BORDER=1 WIDTH=100% CELLSPACING=0> # Вычислим ширину ячейки в процентах от ширины окна ^var[menu_item_width;^eval[round(100/^item[count])]] <TR ALIGN=CENTER> # Пошли перебирать разделы ^menu[<TD WIDTH=^var[menu_item_width]%> # Будем ли ставить ссылку? ^if[^item[no]==$section_no; # Ссылку на этот раздел ставить не будем ^item[title]; # А на другие разделы будем <A HREF="^item[file]">^item[title]</A> ] </TD> ] </TR> </TABLE> # # Выведем заголовок раздела # # Сначала найдем раздел по его номеру ^locate[no;$section_no] # А теперь выведем заголовок <H1>^item[title]</H1> # # Наконец, вставим собственно содержимое раздела # $section_body # # Альтернативная навигация # <HR> <CENTER> # Ссылка на предыдущий раздел, если он есть ^if[^item[number]==1; <^;<^;<^;<^;; <A HREF="^item[prev;file]"><^;<^;<^;<^;</A> ]  ^; ^; ^; ^; # Ссылка на следующий раздел, если он есть ^if[^item[number]==^item[count]; >^;>^;>^;>^;; <A HREF="^item[next;file]">>^;>^;>^;>^;</A> ] </CENTER> <HR> ] </BODY> </HTML>
|
|
|
|
Шаблон раздела Новости содержит единственный макрос main. Он вызывает описанный в файле _macro.cfg макрос section_html, передавая ему номер раздела новостей и код, содержащий ссылки на новости. Напомним, что разделу новостей мы присвоили номер 1.
|
|
|
|
@main[] раздел "Новости" # Вызываем макрос section_html для формирования кода раздела ^macro[section_html;1; # Номер раздела "Новости" - 1
# А теперь сформируем тело раздела, т.е. список новостей ^context[news; ^load[named;news.cfg] ^menu[ # Ссылка на новость <P> <A HREF="newsdetails.html?n_no=^item[no]">^item[title]</A> # Дата <I>^item[date]</I> </P> # Начало текста новости <P><UL>^truncate[^item[text];170]</UL></P> ] ] ]
|
|
|
|
Шаблон для новостей принимает в качестве параметра номер новости, которую требуется отобразить. Он загружает все новости в таблицу и с помощью оператора locate по столбцу no находит нужную новость.
|
|
|
|
<HTML> <HEAD><TITLE>Учебный сайт</TITLE><HEAD> <BODY> ^context[news; ^load[named;news.cfg] ^locate[no;^form[n_no]] <H1>^item[title]</H1> <P>^item[date]</P> <P>^item[text]</P> ] </BODY> </HTML>
|
|
|
|
Теперь обратим внимание на некоторые недостатки нашего решения и посмотрим, каким образом эти недостатки можно было бы устранить.
Возможность деления на ноль. При вычислении ширины ячейки в HTML-таблице мы выполняем деление, не проверив предварительно делитель на неравенство нулю. Поэтому, если в файле _sections.cfg не окажется ни одной записи, Парсер выдаст сообщение об ошибке, что некрасиво и дискредитирует разработчиков сайта в глазах посетителей. В этой ситуации предусмотрительный кодер поступил бы примерно следующим образом:
|
# Назначим таблицу sections текущей ^context[sections; # Загрузим в таблицу данные о разделах ^load[named;sections.cfg] # Проверим, есть ли в таблице хоть одна строка ^empty[Приветствуем вас на самом скучном в мире сайте; # Если есть, то продолжим. Сформируем меню разделов ... ] ... ]
|
|
|
|
В меню всегда только одна строка. А что произойдет, если разделов будет не четыре, а двадцать пять? В строке окажется двадцать пять ячеек со втиснутыми в них ссылками. Как бы ни отреагировал на это браузер, результат будет выглядеть непрезентабельно. Одно из возможных решений проблемы зафиксировать количество столбцов в HTML-таблице и воспользоваться оператором table для того, чтобы расположить ссылки в нескольких строках.
|
# Сформируем многострочное меню разделов # <TABLE BORDER=1 WIDTH=100% CELLSPACING=0> <TR ALIGN=CENTER> # Пошли перебирать разделы ^table[<TD WIDTH=25%> # Будем ли ставить ссылку? ^if[^item[no]==$section_no; # Ссылку на этот раздел ставить не будем ^item[title]; # А на другие разделы будем <A HREF="^item[file]">^item[title]</A> ] </TD>; 4; ^rem[не более четырех ссылок в строке]  ^; ] </TR> </TABLE>
|
|
|
|
Ссылки на новости выводятся в произвольном порядке. Точнее, в том порядке, в котором новости расположены в файле _news.cfg. Между тем ссылки на новости обычно выводят в порядке убывания дат. Сортировка таблицы с помощью оператора sort в данном случае не поможет, поскольку этот оператор не предусматривает сортировку по датам. Наиболее эффективное и универсальное решение в данном случае хранить новости в базе данных.
- Добавьте на все страницы сайта адрес вебмастера и копирайт. Изменение этих данных должно требовать редактирования только одного файла. В копирайте всегда должен быть указан текущий год.
- Организуйте альтернативную навигацию по новостям. Для каждой новости выводите ее порядковый номер и количество новостей, например, 4 из 27. Учтите, что в файле _news.cfg указаны уникальные идентификаторы новостей, а не их порядковые номера.
- Сделайте в новостях меню разделов (подумайте, возможно, имеет смысл начать с вынесения меню разделов в отдельный макрос).
- Измените компоновку разделов. Сделайте меню вертикальным и поместите его слева. Создайте (место выберите сами) блок со ссылками на три последние новости. Будем считать, что новости расположены в файле _news.cfg в порядке убывания дат.
- Выводите текст только для тех новостей, которые посетитель еще не просмотрел (используйте оператор cookie).
|