DOC.PROTOTYPES.RU

Главная > Сервера > Apache > SSI и .htaccess >

Динамическое навигационное меню с использованием SSI Apache

Задача

Итак, есть статичный сайт, в котором существует динамическое навигационное меню, динамика которого заключается в том, что бы подсвечивать пункт из него соответствующий текущей странице.

Как сделать такое меню используя только SSI?

Решение более или менее зависит от реализации структуры самого сайта, варианты:

Теперь каждый вариант в отдельности:

Один к одному

Самая простая в реализации задача. реализуется условиями:

Я специально буду использовать в примере разные переменные окружения, что бы показать несколько вариантов.

Код (1)
<table>
    <tr>
        <td>
<!--#if expr="$REQUEST_URI = /^(\/[?].*|\/)$/"-->
            <b>Главная</b>
<!--#else-->
            <a href="/">Главная</a>
<!--#endif-->
        </td>
        <td>
<!--#if expr="$REQUEST_URI = /^about.shtml/"-->
            <b>О нас</b>
<!--#else-->
            <a href="/about.shtml">О нас</a>
<!--#endif-->
        </td>
        <td>
<!--#if expr="$DOCUMENT_URI = '/catalog.shtml'"-->
            <b>Каталог</b>
<!--#else-->
            <a href="/catalog.shtml">Каталог</a>
<!--#endif-->
        </td>
        <td>
<!--#if expr="$SCRIPT_NAME = '/support.shtml'"-->
            <b>Поддержка</b>
<!--#else-->
            <a href="/support.shtml">Поддержка</a>
<!--#endif-->
        </td>
    </tr>
</table>

Как видно, относительную сложность вызывает только пункт "Главная" так как ссылка на эту страницу в регулярном выражении будет будет подходить для всех страниц. Для в переменных окружения $DOCUMENT_URI и $SCRIPT_NAME регулярное выражение и не нужно, так как в эти переменные не входит $QUERY_STRING как в $REQUEST_URI.

Все бы хорошо, но изменять такое меню несколько неудобно из-за нагромождения SSI директив. Поэтому предлагаю сделать для пункта меню шаблон и упростить определение списка.

Так в компоненте меню будет такой код:

Код (2)
<!--#set var="PAGE_1" value="Главная@/"-->
<!--#set var="PAGE_2" value="О нас@/about.shtml"-->
<!--#set var="PAGE_3" value="Каталог@/catalog.shtml"-->
<!--#set var="PAGE_4" value="Поддержка@/support.shtml"-->

<table>
    <tr>
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_1"-->
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_2"-->
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_3"-->
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_4"-->
    </tr>
</table>

А компонента шаблона (/ssi/navi.template.shtml) выглядит так:

Код (3)
    <td>
<!--#if expr="$QUERY_STRING = /([^@]+)@([^@]+)/ || $QUERY_STRING_UNESCAPED = /([^@]+)@([^@]+)/"-->
    <!--#set var="PAGE_X_NAME" value="$1"-->
    <!--#set var="PAGE_X_URI" value="$2"-->
<!--#endif-->
<!--#if expr="$REQUEST_URI = /^(${PAGE_X_URI}[?].*|${PAGE_X_URI})$/"-->
        <b><!--#echo var="PAGE_X_NAME"--></b>
<!--#else-->
        <a href="<!--#echo var="PAGE_X_URI"-->"><!--#echo var="PAGE_X_NAME"--></a>
<!--#endif-->
    </td>

Правда, есть одно "но" - регулярные выражения возвращают найденное значение в скобках только начиная с версии Apache 2.x, увы, для версии Apache 1.3 данное решение не рабочее. Для Apache 1.3 можно использовать такое решение:

Код (2)
<table>
    <tr>
<!--#set var="PAGE_X_NAME" value="Главная"--><!--#include virtual="/ssi/navi.template.shtml?/"-->
<!--#set var="PAGE_X_NAME" value="О нас"--><!--#include virtual="/ssi/navi.template.shtml?/about/"-->
<!--#set var="PAGE_X_NAME" value="Каталог"--><!--#include virtual="/ssi/navi.template.shtml?/catalog/"-->
<!--#set var="PAGE_X_NAME" value="Поддержка"--><!--#include virtual="/ssi/navi.template.shtml?/support/"-->
    </tr>
</table>

А в компоненте шаблона:

Код (3)
    <td>
<!--#set var="PAGE_X_URI" value="$QUERY_STRING"-->
<!--#endif-->
<!--#if expr="$REQUEST_URI = /^(${PAGE_X_URI}[?].*|${PAGE_X_URI})$/"-->
        <b><!--#echo var="PAGE_X_NAME"--></b>
<!--#else-->
        <a href="<!--#echo var="PAGE_X_URI"-->"><!--#echo var="PAGE_X_NAME"--></a>
<!--#endif-->
    </td>

Еще можно обратить внимание на то, что QUERY_STRING проверяется еще и как QUERY_STRING_UNESCAPED потому, что ... [почему?]

Один ко многим

Если пункт меню подразумевает не одну страницу, а группу страниц (раздел сайта), то добавляется еще одно свойство: Активный пункт, но со ссылкой на главную страницу группы (раздела). В этом случае тоже может быть несколько условий и вариантов решений:

Как видно, основных решений для группировки всего два:

Для главной же страницы раздела:

В соответствии с этим можно определить два алгоритма, алгоритм с использованием $REQUEST_URI:

Код (4)
<!--#set var="PAGE_1" value="О нас@/about/"-->
<!--#set var="PAGE_2" value="Каталог@/catalog/"-->
<!--#set var="PAGE_3" value="Поддержка@/support/"-->

<table>
    <tr>
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_1"-->
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_2"-->
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_3"-->
    </tr>
</table>

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

Код (5)
    <td>
<!--#if expr="$QUERY_STRING = /([^@]+)@([^@]+)/ || $QUERY_STRING_UNESCAPED = /([^@]+)@([^@]+)/"-->
    <!--#set var="GROUP_X_NAME" value="$1"-->
    <!--#set var="GROUP_X_URI" value="$2"-->
<!--#endif-->
<!--#if expr="$REQUEST_URI = /^(${GROUP_X_URI}[?].*|${GROUP_X_URI})$/"-->
        <b><!--#echo var="GROUP_X_NAME"--></b>
<!--#elif expr="$REQUEST_URI = /^${GROUP_X_URI}/"-->
        <b><a href="<!--#echo var="GROUP_X_URI"-->"><!--#echo var="GROUP_X_NAME"--></a></b>
<!--#else-->
        <a href="<!--#echo var="GROUP_X_URI"-->"><!--#echo var="GROUP_X_NAME"--></a>
<!--#endif-->
    </td>

Как видно, для страниц группы (раздела) появился третий вид (выделенная ссылка), который выделяет соответствующий пункт, но оставляет ссылку на соответствующу главную страницу группы (раздела). Следует учесть, что данное решение работает только при условии того, что все группы у нас разбиты по папкам и главные их станицы, являются индексными файлами папки.

Выше мы рассмотрели вариант, когда страницы групп у нас упорядочены по папкам, но не всегда такой порядок присутствует на сайте, что, кстати, плохо. Следовательно, по REQUEST_URI можно найти только главную страницу группы, остальные придется определять переменными окружения:

Код (6)
<!--#set var="PAGE_1" value="О нас@about@/about.shtml"-->
<!--#set var="PAGE_2" value="Каталог@catalog@/catalog.shtml"-->
<!--#set var="PAGE_3" value="Поддержка@support@/support.shtml"-->

<table>
    <tr>
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_1"-->
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_2"-->
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_3"-->
    </tr>
</table>

Как видно, компонента вызова немного изменена, в частности добален еще один параметр, который будет для нас синонимом (alias) группы. На самом деле, в качестве синонима можно использовать как название группы, так и ссылку на главную страницу группы, но сайт у нас в беспорядке, возможны постоянные изменения названий и ссылок, посему опираться на эти параметры не стоит. Соответственно компонента шаблона:

Код (7)
    <td>
<!--#if expr="$QUERY_STRING = /([^@]+)@([^@]+)@([^@]+)/ || $QUERY_STRING_UNESCAPED = /([^@]+)@([^@]+)@([^@]+)/"-->
    <!--#set var="GROUP_X_NAME" value="$1"-->
    <!--#set var="GROUP_X_ALIAS" value="$2"-->
    <!--#set var="GROUP_X_URI" value="$3"-->
<!--#endif-->
<!--#if expr="$REQUEST_URI = /^(${GROUP_X_URI}[?].*|${GROUP_X_URI})$/"-->
        <b><!--#echo var="GROUP_X_NAME"--></b>
<!--#elif expr="$GROUP_X_ALIAS = $PAGE_GROUP"-->
        <b><a href="<!--#echo var="GROUP_X_URI"-->"><!--#echo var="GROUP_X_NAME"--></a></b>
<!--#else-->
        <a href="<!--#echo var="GROUP_X_URI"-->"><!--#echo var="GROUP_X_NAME"--></a>
<!--#endif-->
    </td>

И в каждой, не главной, странице группы прописываем дополнительную переменную в самом начале кода:

Код (8)
<!--#set var="PAGE_GROUP" value="[alias соответствующей группы страницы]"-->

Вот так, причем данный алгоритм можно использовать и по упорядоченному сайту, при этом не прописывая соотвествие каждой страницы группе, а установив переменную PAGE_GROUP в .htaccess папки.

Опять же, для Apache 1.3 потребуется передавать не сложный параметр в компоненту, а простой, плюс еще две переменных устанавливать до вызова компоненты, как в предыдущем варианте. Однако, можно сократить количество кода, если alias группы будет основываться на её названии или на url главной страницы.

Многие ко многим

Вообще, этот вариант, мягко скажем, мало применим для статичных сайтов, ввиду сложности структуры последнего, но, все же, не исключаем и его.

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

Код (9)
<!--#set var="PAGE_1" value="О нас@/about/"-->
<!--#set var="PAGE_2" value="История@/about/history/"-->
<!--#set var="PAGE_3" value="Наши работники@/about/personal/"-->
<!--#set var="PAGE_4" value="Контакты@/about/contact/"-->
<!--#set var="PAGE_5" value="Каталог@/catalog/"-->
<!--#set var="PAGE_6" value="Товары@/catalog/goods/"-->
<!--#set var="PAGE_7" value="Услуги@/catalog/uslugi/"-->
<!--#set var="PAGE_8" value="Поддержка@/support/"-->
<!--#set var="PAGE_9" value="Сервисные центры@/support/service/"-->
<!--#set var="PAGE_10" value="Документы@/support/documents/"-->

<table>
    <tr class="menu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_1"-->
    </tr>
    <tr class="submenu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_2"-->
    </tr>
    <tr class="submenu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_3"-->
    </tr>
    <tr class="submenu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_4"-->
    </tr>
    <tr class="menu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_5"-->
    </tr>
    <tr class="submenu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_6"-->
    </tr>
    <tr class="submenu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_7"-->
    </tr>
    <tr class="menu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_8"-->
    </tr>
    <tr class="submenu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_9"-->
    </tr>
    <tr class="submenu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_10"-->
    </tr>
</table>

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

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

Код (10)
<!--#set var="PAGE_1" value="О нас@about@/about/"-->
<!--#set var="PAGE_2" value="История@about.history@/history/"-->
<!--#set var="PAGE_3" value="Наши работники@about.personal@/personal/"-->
<!--#set var="PAGE_4" value="Контакты@about.contact@/contact/"-->
<!--#set var="PAGE_5" value="Каталог@/catalog/"-->
<!--#set var="PAGE_6" value="Товары@catalog.goods@/goods/"-->
<!--#set var="PAGE_7" value="Услуги@catalog.uslugi@/uslugi/"-->
<!--#set var="PAGE_8" value="Поддержка@/support/"-->
<!--#set var="PAGE_9" value="Сервисные центры@catalog.service@/service/"-->
<!--#set var="PAGE_10" value="Документы@catalog.documents@/documents/"-->

<table>
    <tr class="menu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_1"-->
    </tr>
    <tr class="submenu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_2"-->
    </tr>
    <tr class="submenu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_3"-->
    </tr>
    <tr class="submenu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_4"-->
    </tr>
    <tr class="menu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_5"-->
    </tr>
    <tr class="submenu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_6"-->
    </tr>
    <tr class="submenu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_7"-->
    </tr>
    <tr class="menu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_8"-->
    </tr>
    <tr class="submenu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_9"-->
    </tr>
    <tr class="submenu">
<!--#include virtual="/ssi/navi.template.shtml?$PAGE_10"-->
    </tr>
</table>

При этом в компоненте шаблона, GROUP_ALIAS с PAGE_GROUP мы сравниваем не один к одному, а через регулярное выражение:

Код (11)
    <td>
<!--#if expr="$QUERY_STRING = /([^@]+)@([^@]+)@([^@]+)/ || $QUERY_STRING_UNESCAPED = /([^@]+)@([^@]+)@([^@]+)/"-->
    <!--#set var="GROUP_X_NAME" value="$1"-->
    <!--#set var="GROUP_X_ALIAS" value="$2"-->
    <!--#set var="GROUP_X_URI" value="$3"-->
<!--#endif-->
<!--#if expr="$REQUEST_URI = /^(${GROUP_X_URI}[?].*|${GROUP_X_URI})$/"-->
        <b><!--#echo var="GROUP_X_NAME"--></b>
<!--#elif expr="$PAGE_GROUP = /$GROUP_X_ALIAS/"-->
        <b><a href="<!--#echo var="GROUP_X_URI"-->"><!--#echo var="GROUP_X_NAME"--></a></b>
<!--#else-->
        <a href="<!--#echo var="GROUP_X_URI"-->"><!--#echo var="GROUP_X_NAME"--></a>
<!--#endif-->
    </td>

Но я бы все-таки сделал бы реструктуризацию, до нормального состояния.

Важные грабли

Имейте ввиду, что mod_include Apache версий 1.3 и 2.x различен даже на уровне директив, так для версии 1.3 директива:

Код (12)
<!--# if expr="$SOME_VAR" -->
[code]
<!--# endif -->

будет рабочей, то для 2.х - нет, так как пробел после символа # и директивой - запрещен, поэтому для версии 2.x (но не для 1.3) будет работать директива:

Код (13)
<!--#if expr="$SOME_VAR"-->
[code]
<!--#endif-->

Причем для разных директив разные условия применения пробела между символом # и директивой, так, например, для echo пробел запрещен всегда. То же касается и mod_ssi для nginx, у него вообще принцип работы условий и регулярных выражений в них свой.

Сергей Томулевич aka Phoinix (13.09.2009 г.)
Valid HTML 4.01 Transitional
Copyright © 2011 Сергей Томулевич