• Блог
  • Переводим pdoMenu на Fenom

В этой статье я покажу как используя Fenom можно построить многоуровневое меню с помощью pdoMenu с единственным чанком и забыть про 10 шаблонов (tplOuter, tplInner, tpl, tplHere, tplParentRow, tplParentRowHere, tplParentRowActive, tplInnerRow, tplInnerRowHere, tplCategoryFolder).

Материал рассчитан на разработчиков, свободно владеющими навыками программирования на PHP, а также знакомыми со структурой MODX. Желательно знать азы шаблонизатора Fenom. Новички могут использовать данный материал в режиме «Copy/Paste».

Весь процесс снят на видео. Я постарался сделать это максимально просто.

Исходный код

Сниппет

Я не буду публиковать код всего сниппета pdoMenuFenom, а выложу только добавленный функционал. Вы можете вставить его самостоятельно.

$activeResources = $modx->getParentIds($modx->resource->id, 20, ['context' => $modx->context->key]);
$activeResources[] = $modx->resource->id;
$modx->setPlaceholder('activeResources', array_flip($activeResources));

if (!empty($toArray)) return $tree;

Вставить нужно перед этими строчками (ориентировочно строка 168):

if (!empty($tree)) {
    $output = $pdoMenu->templateTree($tree);
}

Соответственно, при вызове сниппета нужно указать параметр toArray со значением `1` или true в зависимости от синтаксиса вызова.

Чанк Menu

{set $ar = $_modx->getPlaceholder('activeResources')}
{if $ar[$item['id']]!}
    {set $classes = $item['id'] == $_modx->resource.id ? 'here' : 'active'}
{else}
    {set $classes = ''}
{/if}

{if $item['children']?}
<li class="tplParentRow {$classes}">
    <a href="{$item['id'] | url}">{$item['pagetitle']}</a>
    <ul class="tplInner">
    {foreach $item['children'] as $child}
        {set $child['level'] = $item['level'] + 1}
        {$_modx->getChunk('Menu', ['item'=>$child])}
    {/foreach}
    </ul>
</li>
{else}
<li class="{$item['level'] > 1 ? 'tplInnerRow' : 'tpl'} {$classes}">
    <a href="{$item['id'] | url}">{$item['pagetitle']}</a>
</li>
{/if}

Вызов в шаблоне

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

<ul class="tplOuter">
{set $items = $_modx->runSnippet('pdoMenuFenom', [
    'parents' => 0,
    'displayStart' => true,
    'toArray' => true,
])}
{foreach $items as $item}
    {set $item['level'] = 1}
    {$_modx->getChunk('Menu', ['item'=>$item])}
{/foreach}
</ul>

Сниппет можно вызывать как кэшированным так и нет. Всё зависит от поставленной задачи.

Внимание!

Чтобы феном работал в шаблонах необходимо включить системную настройку «Использовать Fenom на страницах».

3   9552

Комментарии ()

  1. Vladimir 26 мая 2018 # +1
    Спасибо, как всегда интересно и практически можно сразу использовать!
    1. Сергей Шлоков 26 мая 2018 # +3
      Рад, что понравилось! Думаю, что было бы логично, если бы во всех сниппетах pdoTools была возможность вывода сырых данных для дальнейшей работы с Fenom. Тем более, что именно pdoTools и подключает этот шаблонизатор.
      1. Alex Zhuravlev 1 24 июля 2018 # +1
        Спасибо. Интересно очень. Доходчиво и не нудно… не хватает немного фоновой музыки как в видео про лару.
        Удобная реализация, но мне кажется что в pdotools это не попадет )
    2. sergikovich 31 июля 2018 # 0
      Отличное решение. Подскажите а как в данном способе использовать TV? Спасибо
      1. sergikovich 31 июля 2018 # 0
        Разобрался, спасибо.
      2. Роман 10 февраля 2019 # 0
        Здравствуйте, спасибо. А как теперь его стилизовать, например бутстрапом 4?
        1. Сергей Шлоков 11 февраля 2019 # 0
          Добрый день! Роман, не расстраивайте меня:(
          1. Роман 11 февраля 2019 # 0
            Извините,… я нуб:a
            1. Сергей Шлоков 11 февраля 2019 # 0
              Мы все прошли через это. Просто вектор обучения должен быть такой — теория -> практика, а не наоборот.
              1. Роман 11 февраля 2019 # 0
                После включения настройки «Использовать Fenom на страницах», ни верстки ни стилей, просто белый экран… отключаю, все норм. хм… А белым он становиться после размещения в шаблоне вызова:
                <ul class="tplOuter">
                {set $items = $_modx->runSnippet('pdoMenuFenom', [
                    'parents' => 0,
                    'displayStart' => true,
                    'toArray' => true,
                ])}
                {foreach $items as $item}
                    {set $item['level'] = 1}
                    {$_modx->getChunk('menu-fenom', ['item'=>$item])}
                {/foreach}
                </ul>
                'menu-fenom' — название моего чанка меню, в котором я разместил код Вашего Чанка Menu
                1. Сергей Шлоков 11 февраля 2019 # 0
                  Смотрите журнал ошибок.
        2. Дмитрий 17 февраля 2019 # 0
          Сергей, приветствую.
          Сделал все по инструкции, только через файловый чанк. Все работает, пока особо не настраивал под себя.

          На странице есть два вызова pdoMenu — в верхнем меню (твой вариант) и для вывода нужного контента на самой странице.

          Применяю debugParser.

          В стандартном вызове pdoMenu для вывода нужного контента на странице в режиме debugParser занимает одно поле и выглядит так:
          {!pdoMenu | snippet : Array ( [parents] => 7 [templates] => -6 [sortby] => publishedon [sortdir] => ASK [includeTVs] => seo_name_uslugi [tplOuter] => @FILE chunks/uslugi/all_uslugi_outer.tpl [tpl] => @FILE chunks/uslugi/all_uslugi_tpl.tpl [tplInner] => @FILE chunks/uslugi/all_uslugi_tplInner.tpl [tplInnerRow] => @FILE chunks/uslugi/all_uslugi_tplInnerRow.tpl ) }

          А верхнее меню (твой вариант), выглядит следующим образом.
          Помимо отображения вызова pdoMenu в одном поле:
          {$_modx->runSnippet("pdoMenuFenom", Array ( [parents] => 0 [displayStart] => 1 [toArray] => 1 [includeTVs] => topMenuTitle ) )}
          Ниже идет много полей, в каждом из которых массивы ресурсов (так понимаю, которые должны быть в меню, субменю, субсубменю). То есть получается в debugParser такая простыня из полей с массивами и на выполнение каждого поля уходит от 0.003… до 0.00004… что значительно увеличивает Total parse time

          далее идут поля с сформированными ссылками
          39 	{1 | url} 	0 	0 	0.0000350
          40 	{22 | url} 	0 	0 	0.0000210
          41 	{18 | url} 	0 	0 	0.0000188
          42 	{7 | url}
          ... и т.д. ...


          Подскажи, это так и надо?
          1. Сергей Шлоков 17 февраля 2019 # 0
            что значительно увеличивает Total parse time
            А значительно это насколько?
            1. Дмитрий 17 февраля 2019 # 0
              А значительно это насколько?
              Сергей, твоя реализация в любом случае работает быстрее, чем стандартный вывод, да и намного удобна.

              Однако я спросил…
              Подскажи, это так и надо?

              Если так и должно быть, то можно еще как-то оптимизировать и не выводить лишние (ненужные) данные. Может фраза «не выводить» неверна, ну я думаю ты меня понял.

              Может я путаюсь в терминологии, но я не программист и не очень давно знаком с MODX, прошу строго не судить.
              1. Сергей Шлоков 18 февраля 2019 # 0
                Ответ на поверхности. debugParser фиксирует всё, что проходит через парсер. В стандартном варианте тег один, а вся логика спрятана в классе. debugParser её не видит. А в моём варианте логика вынесена наружу и все теги фиксируются. Вот и всё. Какая тут нужна оптимизация, я не совсем понимаю.
          2. Момотов Роман 09 марта 2019 # 0
            Здравствуйте, не подскажете как сделать что бы подменю 3 уровня(level3) выводилось на одном уровне в dom дереве с подменю второго уровня (level2) т.е. не было вложено в него и в то же время, подменю 2 и 3 уровней оба находились внутри первого уровня меню?
            1. Сергей Шлоков 10 марта 2019 # 0
              Лично я бы сгенерировал новый массив, в котором объединил бы 2-й и 3-й уровни.
              1. Момотов Роман 10 марта 2019 # 0
                если вам не сложно, можете объяснить по подробнее, а то моё знакомство с Fenom распространяется лишь на настройку minishop2 да и то уже по готовым чанкам
                1. Момотов Роман 18 марта 2019 # 0
                  и снова здравствуйте, как объединить 2-й и 3-й уровни, это в чанке прописывать, или в шаблоне?
                  1. Сергей Шлоков 19 марта 2019 # 0
                    Можно в чанке, можно доработать сниппет.
                    1. Момотов Роман 19 марта 2019 # 0
                      да, очень информативно
                      1. Сергей Шлоков 19 марта 2019 # 0
                        Роман, если Вы решили, что я мучаюсь бездельем и жду возможности каждому новичку решать задачи, то я Вас огорчу — Вы не по адресу. Я могу помочь советом. Это моя принципиальная жизненная позиция. Человек должен сам искать решение. И я, когда учился программировать, никогда не просил сделать за меня какие-то задачи.
                        Меня вообще удивляет ситуация, когда люди без опыта устраиваются на работу, получают проекты, а потом везде пишут: «Помогите, сроки горят, я не успеваю/не знаю/, напишите как нужно.» Мне по несколько запросов в месяц таких приходят. Хотите обижайтесь, хотите нет, но у меня по этому вопросу позиция жесткая — ищите халяву в другом месте.

                        да, очень информативно
                        Вы спрашивали —
                        это в чанке прописывать, или в шаблоне?
                        Я ответил. Попробуйте обратиться в сообщество. Возможно там кому-то эта задача покажется интересной.
                        1. Момотов Роман 19 марта 2019 # 0
                          Справедливо:a
              2. Дмитрий 21 декабря 2019 # 0
                Сергей. приветствую.
                Подскажи, как лучше исключить определенный ресурс из выборки.

                Создал здесь тему
                Но советы, данные помогающими, что-то не помогают.

                Не нашел ничего лучше как сделать так:
                {set $items = $_modx->runSnippet('!pdoMenuFenom', [
                    'parents' => 0,
                    'displayStart' => true,
                    'toArray' => true,
                    'includeTVs' => 'topMenuTitle,svg_icon',
                    'where' => [
                    'parent:=' => 0,
                    'AND:hidemenu:=' => 0,
                    'AND:published:=' => 1,	
                    'OR:isfolder:=' => 1,
                    'AND:hidemenu:=' => 0,
                    'AND:published:=' => 1	
                ]	
                ])}
                {foreach $items as $item}
                    {set $item['level'] = 1}
                	{if $item['id'] !=24}{*<!-- Если это не ресурс с id:24 -->*}
                            {$_modx->getChunk('@FILE chunks/menu/topMenu.tpl', ['item'=>$item])}
                	{/if}
                {/foreach}
                Но так отрабатывает дольше, ведь при каждой итерации идет проверка if $item['id'] !=24

                Как еще можно исключить ресурс?
                1. Сергей Шлоков 21 декабря 2019 # 0
                  Самое простое — это в параметре where исключить
                  'where' => [
                      'parent:=' => 0,
                      'id:!=' => 24,
                      ....
                  
                  Можно через специальный параметр
                  ....
                  'parents' => 0,
                  'resources' => '-24',
                  ....
                  
                  1. Дмитрий 21 декабря 2019 # +1
                    Да дело в том, что
                    'resources' => '-24',
                    не помогает

                    А вот это помогло:
                    ...
                    'where' => [
                        'parent:=' => 0,
                        'AND:id:!=' => 24,
                        'AND:hidemenu:=' => 0,
                        'AND:published:=' => 1,	
                        'OR:isfolder:=' => 1,
                        'AND:hidemenu:=' => 0,
                        'AND:published:=' => 1	
                    ]
                    ...
                    Благодарю!

                    Что-то совсем забыл про id, пробовал так:
                    'AND:resources:!=' => 24,
                    но так не работает… ступил, признаю...:(

                    Еще раз благодарю!
                    1. Сергей Шлоков 21 декабря 2019 # 0
                      Можно ещё использовать параметр hideUnsearchable. Тогда управлять выводом ресурсов можно через чекбокс «Доступен для поиска» (или как то так) в форме редактирования ресурса.
                      1. Дмитрий 21 декабря 2019 # 0
                        Сергей, можно подробнее про hideUnsearchable.

                        Снял флаг в чекбоксе, и сделал так
                        ...
                            'parents' => 0,
                            'hideUnsearchable' => 0,
                            'displayStart' => true,
                            'toArray' => true,
                            'includeTVs' => 'topMenuTitle,svg_icon',
                        'where' => [
                            'parent:=' => 0,
                        ...
                        НО НЕ работает, ресурс НЕ исключается.

                        А так
                        ...
                        'where' => [
                            'parent:=' => 0,
                            'AND:hideUnsearchable:!=' => 24,
                        ...
                        вообще пропадает всё меню

                        Подскажи как реализовать с hideUnsearchable, чувствую так правильнее будет, но как реализовать не знаю.
                        1. Сергей Шлоков 21 декабря 2019 # 0
                          'hideUnsearchable' => true,  // или 1 
                          
                2. Дмитрий 21 декабря 2019 # 0
                  и так тоже не исключается
                  ...
                      'parents' => 0,
                      'hideUnsearchable' => 1,
                      'displayStart' => true,
                      'toArray' => true,
                      'includeTVs' => 'topMenuTitle,svg_icon',
                  'where' => [
                      'parent:=' => 0,
                  ...
                  1. Дмитрий 21 декабря 2019 # 0
                    В общем суть всей проблемы в том, что id:24 это HTML карта сайта.
                    Этот ресурс должен выводиться в sitemap.xml, но в меню не должен выводиться.

                    sitemap.xml выводится так:
                    {'!pdoSitemap'|snippet}
                    Вот думаю может с выводом этого сниппета лучше заморочиться?
                    Только опять не пойму, если поставить чекбокс в «Скрыть из меню», то ресурс 24 не будет выводиться в sitemap.xml

                    Как тут выйти из ситуации?
                    1. Сергей Шлоков 21 декабря 2019 # 0
                      Скорее всего проблема в условии where из-за AND и OR. Проверить это можно посмотрев получаемый SQL запрос включив параметр showLog.

                      Решения.
                      1. Использовать вариант выше, который работает.
                      2. Писать условие where одной строкой с правильной группировкой.
                    2. Максим 02 февраля 2020 # 0
                      Вообще в общих параметрах pdoTools есть return со значениями: data, json и т.д.
                      docs.modx.pro/komponentyi/pdotools/obshhie-parametryi

                      Но по факту эта опция не работает в pdoMenu:(
                      Да и в других криво работает.
                      Например, return=data должно возвращать массив. Но возвращается слово «array». И не важно где вызывать сниппет: в php или в fenom.

                      Для себя проблему кучи чанков решил использованием параметров:
                      {$_modx->runSnippet('pdoMenu@menu')}
                      Один раз прописал и не парюсь.

                      П.С.: Почему то смайлики всегда вставляются в конец, а не там где курсор.
                      1. Serhii 21 апреля 2020 # +1
                        Спасибо большое. Я недавно в этой теме и получил макет с многоуровневым меню из сложной структурой.
                        Благодаря вашему методу без проблем ( ну не совсем чтоб на раз два, раза 3 смотрел видео, разбирался с fenom ) реализовал нужное и теперь вообще прям не боюсь этих менюшек )
                        :s
                        1. Сергей Шлоков 22 апреля 2020 # 0
                          Рад что пригодилось.:E
                        2. Вадим 07 апреля 2023 # 0
                          Добрый день, Сергей. Спасибо за написание этой статьи, очень помогла при построении меню со сложной структурой.:E

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

                          Выделите опечатку и нажмите Ctrl + Enter, чтобы отправить сообщение об ошибке.