Логотип

Документация по макросам и шаблонам UMI.CMS

Несколько меню на сайте

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

Постановка задачи

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

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

Например, если на сайте необходимо поместить отдельное меню "Услуги" и меню "Товары", то логично предположить, что иерархия должна выглядеть следующим образом:

  • страница "Услуги"

    • услуга 1

    • услуга 2

    • услуга 3

  • страница "Товары"

    • категория товаров 1

    • категория товаров 2

    • категория товаров 3

Соответственно, у всех этих страниц должна быть отмечена опция "Отображать в меню".

Сначала мы рассмотрим именно такой вариант организации структуры сайта, а далее варианты более экзотических способов, когда вариант организации структуры по каким-то причинам эту задачу не решает.

Меню, определяемые структурой

Это самый здравый и простой способ организации нескольких меню на сайте. Сначала мы обсудим способ организации вывода при помощи одного макроса — %content menu()%.

Несколько макросов %content menu% с параметрами

Предположим, что у нас есть такой шаблон дизайна:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output encoding="UTF-8" method="html" indent="yes"/>
  
  <xsl:template match="/">
    <html>
      <head></head>
      <body>
      
        <div class="menu1">
          
        </div>

        <div class="content">
          
        </div>

        <div class="menu2">
          
        </div>

      </body>
    </html>
  </xsl:template>
  
</xsl:stylesheet>

Тогда мы указываем в нужных местах при помощи apply-templates место вывода результатов макроса %content menu()%, которому в качестве параметра передаем URL родительской страницы для одного списка подстраниц и URL родительской страницы — для другого списка.

Замечание

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

Чтобы обрабатывать результаты этих двух вызовов отдельно, задаем им два разных режима: например mode="menu1" — для первого меню, и mode="menu2" — для второго.

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output encoding="UTF-8" method="html" indent="yes"/>
  
  <xsl:template match="/">
    <html>
      <head></head>
      <body>
      
        <div class="menu1">
          <xsl:apply-templates select="document('udata://content/menu/0/2/(uslugi)')/udata" mode="menu1"/>
        </div>

        <div class="content">
          
        </div>

        <div class="menu2">
          <xsl:apply-templates select="document('udata://content/menu/0/2/(tovary)')/udata" mode="menu2"/>
        </div>

      </body>
    </html>
  </xsl:template>
  
</xsl:stylesheet>

Сами шаблоны тогда должны выглядеть так (в зависимости от задач верстки их можно переоформить для каждого меню):

  

  <xsl:template match="udata[@module = 'content'][@method = 'menu']" mode="menu1">
    <ul>
      <xsl:apply-templates select="items/item" mode="menu1"/>
    </ul>
  </xsl:template>

  

  <xsl:template match="item" mode="menu1">
    <li>
      <a href="{@link}">
        <xsl:value-of select="@name"/>
      </a>
    </li>
  </xsl:template>

  

  <xsl:template match="item[@status = 'active']" mode="menu1">
    <li>
       <a href="{@link}" class="active">
         <xsl:value-of select="@name"/>
       </a>
    </li>
  </xsl:template>


  

  <xsl:template match="udata[@module = 'content'][@method = 'menu']" mode="menu2">
    <ul>
      <xsl:apply-templates select="items/item" mode="menu2"/>
    </ul>
  </xsl:template>

  

  <xsl:template match="item" mode="menu2">
    <li>
      <a href="{@link}">
        <xsl:value-of select="@name"/>
      </a>
    </li>
  </xsl:template>

  

  <xsl:template match="item[@status = 'active']" mode="menu2">
    <li>
       <a href="{@link}" class="active">
         <xsl:value-of select="@name"/>
       </a>
    </li>
  </xsl:template>

Другие макросы выводящие списки

Очевидно, что для вывода подобных меню можно использовать не %content menu()%.

Разделы каталога

Если мы хотим вывести "меню" из списка разделов каталога — нам следует воспользоваться макросом %catalog getCategoryList()% с параметрами (URL раздела). Тогда в месте вывода результатов макроса указываем инструкцию:

<xsl:apply-templates select="document('udata://catalog/getCategoryList/0/(shop)')/udata"/>

Обработку результатов этого макроса следует передать шаблону:

<xsl:template match="udata[@module = 'catalog'][@method = 'getCategoryList']">
  <ul>
    
  </ul>
</xsl:template>

Замечание

Для того, чтобы реализовать подсветку текущего пункта меню, в рамках этой задачи, придется воспользоваться глобальной переменной, в которой мы передадим id текущей страницы (см. «Формат UMI Data» — мы ее получим из result/@pageId) и логической конструкцией if.

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

В итоге шаблон будет выглядеть следующим образом:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output encoding="UTF-8" method="html" indent="yes"/>

  
  <xsl:variable name="pid" select="result/@pageId"/>
  
  <xsl:template match="/">
    <html>
      <head></head>
      <body>
      
        <div class="menu1">
          
        </div>

        <div class="content">
          
        </div>

        <div class="menu2">
          <xsl:apply-templates select="document('udata://catalog/getCategoryList/0/(shop)')/udata"/>
        </div>

      </body>
    </html>
  </xsl:template>

  

  <xsl:template match="udata[@module = 'catalog'][@method = 'getCategoryList']">
     <ul>
        <xsl:apply-templates select="items/item" mode="catalog"/>
     </ul>
  </xsl:template>

  
  
  <xsl:template match="item" mode="catalog">
     <li>
        <a href="{@link}">
          <xsl:if test="@id = $pid">
              <xsl:attribute name="class">active</xsl:attribute>
          </xsl:if>
          <xsl:value-of select="." />
        </a>
     </li>
  </xsl:template>

</xsl:stylesheet>

Инструкция attribute создаст атрибут с именем class и значением active в том случае, если id текущего элемента списка будет совпадать с глобальной переменной $pid.

Ленты новостей

Если мы хотим вывести "меню" из списка лент новостей — нам следует воспользоваться макросом %news lastlents()%, передав URL страницы всех новостей в качестве параметра. Тогда в месте вывода результатов макроса указываем инструкцию:

<xsl:apply-templates select="document('udata://news/lastlents/(news)')/udata"/>

Обработку результатов этого макроса следует передать шаблону:

<xsl:template match="udata[@module = 'news'][@method = 'lastlents']">
  <ul>
    
  </ul>
</xsl:template>

Подробнее про ленты новостей можно посмотреть в топике Ленты новостей средствами XSLT-шаблонизатора.

Фотоальбомы, блоги и так далее

Если мы хотим вывести "меню" из списка фотоальбомов, блогов и так далее — нам следует воспользоваться макросами, выводящими списки для соответствующих модулей.

Например для фотоальбомов это будет макрос %photoalbum albums()%, для блогов — %blogs20 blogsList()%.

Тогда в месте вывода результатов макроса указываем инструкции:



<xsl:apply-templates select="document('udata://photoalbum/albums/')/udata"/>



<xsl:apply-templates select="document('udata://blogs20/blogsList/')/udata"/>

Обработку результатов этого макроса следует передать шаблонам:



<xsl:template match="udata[@module = 'photoalbum'][@method = 'albums']">
  <ul>
    
  </ul>
</xsl:template>







<xsl:template match="udata[@module = 'blogs20'][@method = 'blogsList']">
  <ul>
    
  </ul>
</xsl:template>


Подсветка текущего пункта меню — см. пример по каталогу выше.

Разделение по позиции

При помощи функции position(), возвращающей положение элемента в XML дереве можно выводить определенные пункты меню, добавив это условием в вызов обработки результатов макроса.

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output encoding="UTF-8" method="html" indent="yes"/>
  
  <xsl:template match="/">
    <html>
      <head></head>
      <body>
      
        <div class="menu1">
          <xsl:apply-templates select="document('udata://content/menu/0/2/(menutest)')/udata/items/item[position() &lt;= 6]"/>
        </div>

        <div class="content">
          
        </div>

        <div class="menu2">
          <xsl:apply-templates select="document('udata://content/menu/0/2/(menutest)')/udata/items/item[position() &gt; 6]"/>
        </div>

      </body>
    </html>
  </xsl:template>
  
</xsl:stylesheet>

Сущность &lt; используется в XML вместо < (меньше), а сущность &gt; — вместо > (больше). Таким образом в первом вызове apply-templates мы выбираем первые 6 элементов списка, во втором — все оставшиеся.

Подсказка

Функция position() предоставляет также возможность легко выбирать и выделять кратные любому числу элементы. Например следующий шаблон, обрабатывающий отдельный элемент списка item, выделит каждый третий элемент атрибутом class="i3".

<xsl:template match="item[position() mod 3 = 0]" mode="menu-nested">
  <li>
    <a href="{@link}" class="i3">
      <xsl:value-of select="@name" />
    </a>
  </li>
</xsl:template>

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

Недостатком этого подхода, очевидно, является то, что цифра 6 в данном случае жестко прописана в шаблоне. Пользователи, таким образом, лишаются возможности менять позицию как в административном интерфейсе, так и через edit-in-place.

Использование «страницы-разделителя»

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

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

Например это можно реализовать при помощи следующих шаблонов:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output encoding="UTF-8" method="html" indent="yes"/>
  
  <xsl:template match="/">
    <html>
      <head></head>
      <body>
      
        <div class="menu">
          
          <xsl:apply-templates select="document('udata://content/menu/')/udata"/>
        </div>

        <div class="content">
          
        </div>

      </body>
    </html>
  </xsl:template>

  

  <xsl:template match="udata[@module = 'content'][@method = 'menu']">
    <ul>
       <xsl:apply-templates select="items/item" mode="menu"/>
    </ul>
  </xsl:template>

  
  
  <xsl:template match="item" mode="menu">
   <li>
     <a href="{@link}">
       <xsl:value-of select="@name" />
     </a>
   </li>
  </xsl:template>

  

  <xsl:template match="item[@name = 'spacer']" mode="menu">
    <li class="spacer"> - here comes the spacer - </li>
  </xsl:template>

  
  
  <xsl:template match="item[@status = 'active']" mode="menu">
    <li>
      <a href="{@link}" style="active">
        <xsl:value-of select="@name"/>
      </a>
    </li>
  </xsl:template>

</xsl:stylesheet>

Как можно видеть, для страницы с именем 'spacer' (шаблон с условием соответствия match="item[@name = 'spacer']"), в этом примере мы выводим другой элемент <li></li>, который и отделяет "одно" меню от "другого".

Преимуществом этого метода, перед описанным выше (разделением по позиции), является то, что можно легко переместить страницу-разделитель в структуре сайта, как через админку, так и через edit-in-place.

Недостатком этого метода является то, что крайне не рекомендуется использовать незакрытые теги в шаблоне, описывающем страницу-разделитель (простым способом это сделать и вовсе не удастся). Кроме того, эти меню в итоге должны быть расположены рядом в HTML-коде. Важно также иметь в виду, что страницы-разделители попадут в результаты макроса %content sitemap()%.

Использование протокола USel для построения «меню»

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

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

Для детального понимания того, как работает этот способ, рекомендуется ознакомиться с топиком «Выборки из БД: протокол USel».

Внимание

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

Кроме того, страницы выбранные таким способом, не попадут в результаты макроса %content sitemap()%, и карту сайта необходимо будет создавать каким-либо другими способами.

Подготовительный этап

Допустим мы хотим вывести в разных меню разные страницы контента.

Для того, чтобы можно было пометить страницу мы добавим дополнительное поле к типу данных "Страницы контента".

Создадим дополнительную группу под названием "Множественные меню", добавим туда поле с идентификатором, например, 'menu_id'.

Теперь, для нужных страниц сайта, в это поле следует вписать идентификатор — например 'menu1', 'menu2', и так далее.

Теперь значение этого свойства доступно в свойствах тех страниц, для которых мы вписали идентификаторы. Убедиться можно в этом, если запросить любую из этих страниц по протоколу UPage — допустим для страницы с id=100 можно ввести в адресной строке браузера http://ваш_сайт/upage/100.menu_id

Тогда в окне браузера мы должны увидеть примерно следующее:

<udata generation-time="0.004890">
  <property id="9248" name="menu_id" type="string">
    <title>идентификатор меню</title>
    <value>menu2</value>
  </property>
</udata>

Таким образом, для страницы с id=100 мы можем видеть, что значение этого свойства равно 'menu2'.

Написание шаблона USel

Шаблоны запросов к системе по протоколу USel, позволяют получить список объектов системы, удовлетворяющих определенным критериям. В данном случае, нас интересует критерий значения в созданном нами свойстве menu_id.

Чтобы не писать для каждого меню шаблон запроса, мы воспользуемся возможностью передавать параметр в шаблоны USel (см. топик «Выборки из БД: протокол USel»).

Назовем файл шаблона, например, multimenu.xml, разместим его в папке ~/usels/ и добавим в него следующий XML-текст.

<?xml version="1.0" encoding="utf-8"?>

<selection>
  <target expected-result="pages">
    <type module="content" method="page" />
  </target>
  <property name="menu_id" value="{1}"/>
</selection>

Это означает следующее: выбрать страницы (expected-result="pages"), с назначением типа "Страница контента", у которых свойство с идентификатором menu_id равно первому параметру ({1}), переданному в URL.

Для того, чтобы в результаты выборки попали страницы других типов надо добавить строку <type module="модуль" method="метод" />, указав назначение типа.

Обработка результатов

Теперь в шаблоне мы можем вызывать данные на обработку следующим образом:

<xsl:apply-templates select="document('usel://multimenu/menu1')/udata" mode="menu-usel-1"/>

Где menu1 — параметр, который будет подставлен вместо {1}, а mode="menu-usel-1" — режим для обработки полученных результатов.

Соответственно, для второго меню, следует вызов оформить таким образом:

<xsl:apply-templates select="document('usel://multimenu/menu2')/udata" mode="menu-usel-2"/>

И так далее для menu3, menu4, menu5 — если необходимо, каждый со своим режимом, или с общими режимами для некоторых, в зависимости от задач.

Поскольку мы изначально указываем режим обработки, то достаточно будет указать в шаблонах match="udata" и нужный mode.

 <xsl:template match="udata" mode="menu-usel-1">
   <ul>
     
   </ul>
 </xsl:template>

Итоговый набор шаблонов

В итоге мы должны получить 2 файла следующего содержания:

Шаблон USel (~/usels/multimenu.xml):

<?xml version="1.0" encoding="utf-8"?>

<selection>
  <target expected-result="pages">
    <type module="content" method="page" />
  </target>
  <property name="menu_id" value="{1}"/>
</selection>

Шаблон XSLT:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output encoding="UTF-8" method="html" indent="yes"/>
  
  <xsl:template match="/">
    <html>
      <head></head>
      <body>
      
        <div class="menu">

          
          <xsl:apply-templates select="document('usel://multimenu/menu1')/udata" mode="menu-usel-1"/>

          
          <xsl:apply-templates select="document('usel://multimenu/menu2')/udata" mode="menu-usel-2"/>

          

        </div>

        <div class="content">
          
        </div>

      </body>
    </html>
  </xsl:template>


 

 <xsl:template match="udata" mode="menu-usel-1">
   <ul>
     <xsl:apply-templates select="page" mode="menu-usel-1"/>
   </ul>
 </xsl:template>

 <xsl:template match="page" mode="menu-usel-1">
  <li>
    <a href="{@link}">
      <xsl:value-of select="name" />
    </a>
  </li>
 </xsl:template>



 <xsl:template match="udata" mode="menu-usel-2">
   <ul>
     <xsl:apply-templates select="page" mode="menu-usel-2"/>
   </ul>
 </xsl:template>

 <xsl:template match="page" mode="menu-usel-2">
  <li>
    <a href="{@link}">
      <xsl:value-of select="name" />
    </a>
  </li>
 </xsl:template>

 
  
</xsl:stylesheet>