Сниппет DLMenu для вывода меню с помощью DocLister

С Wayfinder все понятно и так; DLBuildMenu хорош как иллюстрация возможностей параметра prepare, но такой способ решения задачи привел к повышенному потреблению ресурсов, что уже не очень хорошо. К недостаткам DLBuildMenu можно отнести и некоторую сложность при переходе с Wayfinder. В общем, предлагаю свое решение, которое должно преуменьшить недостатки DLBuildMenu, сохранив гибкость в манипуляциях с шаблонами.

Сниппет я написал, конечно, на базе DocLister, расширив контроллер site_content. Особенности:
  • система шаблонов и классов, схожая с Wayfinder;
  • количество запросов равно количеству уровней в меню (без учета запросов на добавление tv-параметров и подсчет дочерних документов)
  • собственно, возможность подсчитать количество дочерних ресурсов (для непосредственных родителей);
  • почти все параметры можно задавать как для всех уровней меню, так и для конкретного уровня;
  • можно использовать prepare для обработки данных и подмены шаблонов; как и в DocLister можно с помощью prepare исключать документы из вывода;
  • можно строить меню от нескольких родителей;
  • можно держать развернутыми определенные ветки;
  • можно вывести меню в виде json-массива;
  • кэширование исходных данные для ускорения работы (пойдет в паблик, когда прикрутим к Evo нормальный кэш).

Документация здесь, сам сниппет я положил к DocLister'у.

66 комментариев

avatar
Это вот хорошо. Попробуем. Спасибо.
avatar
Кстати. А почему Wayfinder не обновляется? Может проще было его перекрутить?
Ну это я так, в порядке бреда.
  • 1px
  • 0
avatar
Не обновляется, потому что никто не обновляет, хотя не так давно 64j допилил возможность использовать разные шаблоны для разных уровней. Да и чего лукавить, для подавляющего большинства случаев Wayfinder подходит в текущем виде. Мой сниппет больше на замену DLBuildMenu, с которым совсем фигня получилась.
avatar
Ну вообще да. Но жрёт он ресурсы конечно знатно.
А так, по наблюдениям — 90% сайтов меню вообще никогда не обновляют. «О нас», «Услуги», «Контакты» и хва.
avatar
Точно, я даже сразу не смог найти сайт со сложным меню, чтобы протестировать (:
avatar
Можно протестировать на вот этом примере :) Давно выжидаю решение для подобного (MegaMenu / Regular submenu) jsfiddle.net/vadikom/7k3wu7no/?utm_source=website&utm_medium=embed&utm_campaign=7k3wu7no (отсюда www.smartmenus.org)
avatar
Поставил тест на трёх сниппетах:
Wayfinder_custom
DLMenu
DLBuildMenu

320 категорий, с вложенностью до 4-ёх уровней,
вызов каждого одинаковый, все некэшируемые

<table>
	<tr>
		<td valign="top"> 
			[!Wayfinder_custom?
			&startId=`6`
			!] 
		</td>
		<td valign="top"> 
			[!DLMenu?
			&parents=`6`
			!] 
		</td>
		<td valign="top"> 
			[!DLBuildMenu?
			&parents=`6`
			!]
		</td>
	</tr>
</table>


Первый результат
1. Wayfinder_custom (182.01 ms)
2. DLMenu (174.01 ms)
3. DLBuildMenu (1227.07 ms)

После обновления страницы
1. Wayfinder_custom (83.00 ms)
2. DLMenu (91.01 ms)
3. DLBuildMenu (1016.06 ms)

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

<table>
	<tr>
		<td valign="top">
			[!Wayfinder_custom?
			&startId=`6`
			&parentClass=`parent`
			&rowTpl=`wc.rowTpl`
			!]
		</td>
		<td valign="top">
			[!DLMenu?
			&parents=`6`
			&parentClass=`parent`
			&rowTpl=`dlm.rowTpl`
			&parentRowTpl=`dlm.rowTpl`
			&tvList=`image`
			!]
		</td>
		<td valign="top">
			[!DLBuildMenu?
			&parents=`6`
			&TplOneItem=`dlbm.rowTpl`
			&tvList=`image`
			!]
		</td>
	</tr>
</table>


1. Wayfinder_custom (189.01 ms)
2. phpthumbs (1.00 ms)
3. phpthumbs (1.00 ms)

239. phpthumbs (1.00 ms)
MySQL: 0.1400 s, ~9 request(s)
===============================

1. phpthumbs (2.00 ms)
2. phpthumbs (1.00 ms)
3. phpthumbs (1.00 ms)

239. DLMenu (481.03 ms)
MySQL: 0.0840 s, ~10 request(s)
===============================

1. phpthumbs (1.00 ms)
2. phpthumbs (0.00 ms)
3. phpthumbs (0.00 ms)

267. DLBuildMenu (1536.09 ms)
MySQL: 0.3740 s, ~439 request(s)
===============================

И всё вместе
1. Wayfinder_custom (189.01 ms)

240. DLMenu (472.03 ms)

507. DLBuildMenu (1428.08 ms)

Выводы очевидны, но если учесть что в DLMenu есть возможность использования всей мощности DocLister, то это пока лучшее решение на сегодняшний день, для простых и сложных меню.
  • 64j
  • 0
avatar
Я что-то не понял, как результаты второго и третьего теста интерпретировать. 239 — это что? Время в скобках — это время выполнения или время от начала запуска теста?
avatar
в самом начале поставил! сниппет! с кодом $this->dumpSnippets = true;
и стало показывать количество вызываемых снипетов.

Цифра в начале, это порядковый номер.
Поскольку phpthumbs в каждом уровне, я сократил как три точки…
А в скобках, это время выполнения
avatar
А можно дамп сайта с тестами? Интересно, откуда берется такая разница в тесте с phpthumb. Интересно посмотреть и на результаты с «Использовать AliasListing только для Папок», когда Wayfinder теряет бонус за использование карты ресурсов.
avatar
дамп к сожалению не могу дать, сайт рабочий (или речь о вызовах SQL ?)
Убрал чанк с картинкой.
Отключаю AliasListing и вложенные урл
1. Wayfinder_custom (52.00 ms)
2. DLMenu (85.01 ms)
3. DLBuildMenu (1124.07 ms)

Включаю AliasListing и вложенные урл
1. Wayfinder_custom (186.01 ms)
2. DLMenu (143.01 ms)
3. DLBuildMenu (1096.06 ms)

Добавляю чанк с картинкой, и меняю вызовы в обратном порядке
1. phpthumbs (1.00 ms)

267. DLBuildMenu (1489.08 ms)

506. DLMenu (375.02 ms)
507. Wayfinder_custom (191.01 ms)

745. phpthumbs (0.00 ms)

ссылка на кастомный Wayfinder, можете сами проверить
avatar
А в Wayfinder что-нибудь менялось еще кроме обработки параметров шаблонов?
avatar
ну так то да))
Запросы убрал лишние, а то там на каждый TV из каждого уровня по запросу было, сократил до 1-ого, в котором сразу все тв-шки выбирает.

Ещё что то, но уже не помню, давно было.
avatar
Вижу, что в Wayfinder для обработки шаблонов используется метод parseText, то есть по сути чистый str_replace. А значит вызов phpthumb обрабатывается на втором проходе парсера, который может не фиксируется уже в тесте? В DocLister перед выводом результата делается parseDocumentSource.
Комментарий отредактирован 2017-07-17 21:15:02 пользователем Pathologic
avatar
там два раза парсер проходит

avatar
вот немного другой тест.
Вызов каждого сниппета по отдельности, но данные буду смотреть в переменных [^qt^], [^q^], и т.п.

С картинкой и алиасЛистинг
DLBuildMenu
Memory: 8 mb, MySQL: 0.4000 s, 441 request(s), PHP: 0.9906 s, total: 1.3906 s

DLMenu
Memory: 8 mb, MySQL: 0.0770 s, 12 request(s), PHP: 0.3215 s, total: 0.3985 s

Wayfinder_custom
Memory: 4 mb, MySQL: 0.1500 s, 11 request(s), PHP: 0.2911 s, total: 0.4411 s
avatar
Вот, значит мое предположение оказалось правильным (: Спасибо за тесты.
avatar
верным, но по скорости всё равно Wayfinder_custom вперёд выбирается))
avatar
Ну, не такая там большая разница, особенно если с DLBuildMenu сравнить. Проиграл немного в скорости, зато выиграл в возможностях, так что все хорошо (:
avatar
ну я так и написал выше, пока что лучшее решение на сегодняшний день)
avatar
Добрый день.
Помогите решить вопрос:
не работает tvList в DLMenu
может что-то не подключил, хз....
добавил сниппет DLMenu с таким кодом:
<?php
return require MODX_BASE_PATH.'assets/snippets/DocLister/snippet.DLMenu.php';

файли з закинул необходимые....
и вот вызов:
[[DLMenu? 
&tvList=`titl_ua,titl_ru`								  
&parents=`2`
&maxDepth=`2`
&sortBy=`[+count+]`
&sortDir=`DESC`
&countChildren=`1`								  
&outerTpl=`@CODE:<ul class="dropdown-menu">[+wrap+]</ul>`								  
&parentRowTpl=`menu_top`								  
]]	

все работает, но тв не отображаются
avatar
tv.titl_ua, tv.titl_ru

&sortBy=`[+count+]` — это вообще бред.
avatar
и сортировка нужна по количеству дочерних ресурсов, так же не розобрался ищё
avatar
Количество дочерних тут только для вывода, сортировка не предусмотрена.
avatar
понял. спасибо.
а как реализовать сортировку по количеству дочерних елементов на модкс?
avatar
Запрос нужно изменить, а как — не знаю вот так сходу (:
avatar
понятно.
помозгую....
может кто придумает — виложыте плиз
спасибо за ответы
avatar
Решается так:

<?php
include_once('site_content_menu.php');
/**
 * Created by PhpStorm.
 * User: Pathologic
 * Date: 25.07.2017
 * Time: 15:56
 */
class site_content_menu_customDocLister extends site_content_menuDocLister
{
    /**
     * Генерация имени таблицы с префиксом и алиасом
     *
     * @param string $name имя таблицы
     * @param string $alias желаемый алиас таблицы
     * @return string имя таблицы с префиксом и алиасом
     */
    public function getTable($name, $alias = '')
    {
        $table = parent::getTable($name, $alias);
        if ($name == 'site_content') {
            $_table = parent::getTable($name, '');
            $table = "{$table} LEFT JOIN (SELECT `parent` as `_parent`, COUNT(*) as `count` FROM {$_table} GROUP BY `_parent`) `p` ON `c`.`id` = `p`.`_parent`";
        }

        return $table; // TODO: Change the autogenerated stub
    }

    /**
     * Добавление виртуальных плейсхолдеров
     * @param $data
     * @return array
     */
    public function prepareData($data)
    {
        $data = parent::prepareData($data); // TODO: Change the autogenerated stub
        if (!$data['count']) $data['count'] = 0;

        return $data;
    }
}


После чего появляется возможность сортировать &orderBy=`count ASC` или делать выборки. Что делать с этим кодом предлагаю разобраться самостоятельно, все ответы есть в документации к DocLister.
avatar
Обработка данных перед выводом


этот раздел?
avatar
Добавить могу только, что при вызове нужно указать параметры:

&controller=`site_content_menu_custom`
&selectFields=`c.*,p.*`
avatar
работает)
спасибо
только ище нужно, чтобы не считало неопубликованые ресурси в загальное количество, и можна було выводит количество ресурсов
avatar
Не выводит, потому что закралась ошибка в основной контроллер. А как убрать из подсчета неопубликованные ресурсы, думаю, что догадаться несложно (:
avatar
Поправил как Вы показали в коде здесь «закралась ошибка в основной контроллер», но все равно не работает..( помогите пожалуйста
avatar
Спасибо за сниппет.

Есть вопрос с
[+count+]

Вывожу в шаблоне
&categoryFolderTpl=`@CODE:<li[+classes+]><a href="[+url+]">[+title+] [+count+]</a>[+wrap+]</li>` 

Атрибуты ссылки на странице category прописал

Проблема в том, что если дочерние документы по какой-то причине в меню не выводятся (ограничен уровень вложенности или отмечено не выводить в меню), то шаблон «не срабатывает» и количество товаров в меню не выводится. В общем-то, наверное, и правильно, но как тогда показать количество товаров в категории?

Можно было бы в этом шаблоне
&innerRowTpl=`@CODE:<li[+classes+]><a href="[+url+]">[+title+] [+count+]</a></li>`
но тогда захватываются и другие ресурсы и показывает в них count=0.
Подскажите, как разрулиться?

Спасибо.
avatar
Можно через prepare проверять признаки категории и подменять шаблон.
avatar

if ($data['maxLevel'] && ((isset($data['template']) && !$data['template']) || (isset($data['link_attributes']) && strpos($data['link_attributes'],'category') !== false))) {
    $data['_renderRowTpl'] = $_DocLister->getCFGDef('categoryFolderTpl');
}

return $data;
Комментарий отредактирован 2017-07-25 21:48:28 пользователем Pathologic
avatar
Спасибо!
avatar
Pathologic, не смотрели на ошыбку с выводом количества?
avatar
Не смотрел, потому что ее нет:
avatar
никак не могу вызвать сниппет DLMenu((
при вызове пишет Страница недоступна. Сайт пока не может обработать этот запрос. HTTP ERROR 500
подскажите, в чём может быть проблема?
avatar
Фаза луны не та.
avatar
с другим контроллером подгружает, а с site_content_menu никак
версия php для DLMenu повыше не нужна часом?
avatar
Может быть, у меня 5.6.
avatar
На сервере должен быть error.log, где и написано, почему не работает.
avatar
да, на 5.5 уже не работает
avatar
Сейчас главное чтобы на 7 работало (:
avatar
Есть в DocLister prepare такой элемент в массиве data как dl.display. Можно как-то в DLMenu получить его же, то есть общее количество документов на текущем уровне?
avatar
Можно:

$data['dl.display'] = count($_DocLister->levels[$data['level']]);


А для чего это?
avatar
Банально надо разделить список на два, вставив
</ul><ul>
в серединку. Не хочется это делать на js. Через DocLister такое делал, а только теперь увидел сниппет DLMenu и захотел воспользоваться. Кстати, спасибо за него большущее. Хорошая штука.
avatar
Можно попробовать еще посмотреть на $data['wrap'] — там массив отрендеренных дочерних документов, который будет шаблонизироваться через innerTpl или outerTpl. Или не будет, если его шаблонизировать вручную в prepare (:
avatar
И следом встал вопрос, что недоступно значение $data['iteration'] в prepare.
avatar
Да, есть проблема такая, исправлю попозже.
avatar
Было не просто, но добавил iteration и _display.
avatar
Может что-то пропустил, прошу прощения.
Проблема:
добавляем в вызов DLMenu
&tvList=`Price`
&orderBy=`Price ASC`
и всё — аут, вывод нарушается
а если еще debug=`1` добавить, то вообще получаю: Error: Invalid argument supplied for foreach()
Как правильно сортировать по tv в DLMenu?
avatar
Исправил сортировку.
Комментарий отредактирован 2017-08-07 12:29:11 пользователем Pathologic
avatar
уровни > 1 не выводит
(doclister с гитхаба свежуий взял)
avatar
Свежий это какой?
avatar
ага вижу
взял самый свежий )
спасибо
avatar
А пролейте свет на то как задавать шаблоны пунктам разного уровня вложенности. Из документации это не ясно. И заодно на реализацию этого пункта из описания:
почти все параметры можно задавать как для всех уровней меню, так и для конкретного уровня
avatar
Ну а что тут не понятно? rowTpl1, rowTpl2, filters3, orderBy10. prepare и tvList точно не работают так, а остальные я особо не проверял. Для rowTpl нужно указывать на 1 меньше.
Комментарий отредактирован 2017-08-10 18:42:23 пользователем Pathologic
avatar
А как об этом можно было узнать не задавая вопрос автору?
avatar
Из описания (:
avatar
Простите мою невнимательность, но можно указать где в описании говорится про
rowTplN
?
avatar
Я думал, что это очевидно. В документации вообще написано открыто: docs.evo.im/extras/dlmenu.html
avatar
Не нашел в доках и здесь… Как убрать из меню пункты, у которых не стоит галочка «Показывать в меню»?
  • googa
  • 0
avatar
Навскидку — использовать параметры addWhereList или filters, указав в качестве условия поле hidemenu. То есть как в ДокЛистере. А вообще, может быть, в DLMenu по умолчанию так и есть (не проверял).
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.