Снова о кешировании. Генерируем меню только один раз.

Иногда бывает необходимость сделать меню большим. Ну, реально большим и разветвленным. Например, как на сайтах fsknmsk.ru, medicinskiy-center.ru, www.withoutdrugs.ru, narcononekb.ru

Если Wayfinder вызывается кешируемым, т. е. [[Wayfinder &startId=`0`]] (без восклицательного знака), и изменения на сайте вносятся нечасто, то проблем особых нет — после первого посещения каждой страницы, вывод сниппета кешируется и для этой страницы код меню уже не генерируется.

Если изменения вносятся часто, но на меню они особо не влияют (например, просто выкладываются новости или статьи, которые в меню не появляются), то используем getCache (статья)

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

Поделюсь своим решением, использующим самый простой вариант пользовательского кеширования в MODX

Создаем чанк menu, в котором прописываем вызов Wayfinder с нужными нам параметрами. А в шаблоне вызываем сниппет menu:

// Проверяем, есть ли в кеше сгенерированный код меню
$output = $modx->cacheManager->get('menu');

if (empty($output)) {
  // Генерируем менюшку
  $output = $modx->getChunk('menu');
  // Но сохраняем ее только если это главная страница сайта
  if ($modx->resource->id == $modx->getOption('site_start')) {
    $modx->cacheManager->set('menu',$output);
  }
}

return $output;

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

Единственный минус — класс active придется подставлять с помощью JavaScript:
<script type="text/javascript">
    $.each($("aside nav a"), function() {
        if ($(this).attr("href") == "[[~[[*id]]]]") {
            var item = $(this).parent("li");
            item.addClass("active");
            if (item.parent().parent().is("li")) {
              item.parent().parent().addClass("active");
            }
        }
    });
</script>

Работает, если на странице подключен JQuery и уровень вложенности не больше двух.

Кстати, если кто подскажет более эффективный, красивый и правильный JavaScript для этой же цели, буду очень благодарен. Особенно, если вы знаете, как уйти от указания каждого уровня вложенности. G+

24 комментария

avatar
Илья, три раза перечитал основную часть топика, но нифига не понял, зачем столько действий, когда можно было просто сделать один кешируемый сниппет menu
$key = 'menu';
if($menu = $modx->cacheManager->get($key)){
    return $menu;
}
$menu = $modx->runSnippet('Wayfinder', array('startId' => 0, $params));
$modx->cacheManager->set($key, $data);
return $menu;
Зачем эта проверка на site_start?
avatar
Если после очистки кеша перейти на внутреннюю страницу, то у текущего пункта меню появится класс active и вывод закешируется. Поэтому кладем в кеш только меню с главной страницы. А чанк просто для удобства эксплуатации и изменения параметров.
avatar
Да, предполагал этот ответ, но не стал писать предположение. Так а что мешает прописать в параметрах вызова Wayfinder 'hereClass' => '' и сбросить этот класс? Будет полностью чистое меню. Все равно же хочешь active проставлять javascript-ом. Получишь чистое меню из любого положения.
avatar
А чанк просто для удобства эксплуатации и изменения параметров
Чем [[Wayfinder?startId=`0`]] юзабильней $modx->runSnippet('Wayfinder', 'startId' => 0);?
Мы говорим о кешировании и оптимизации. При этом фигачим еще один объект (чанк), без которого итак хорошо обходимся.
Позавчера сайт оптимизировал 60 000 страниц. Использование лишнего чанка стоило 5-30 секунд загрузки страницы, в то время как поправив «пару» строк в коде, получил 0.5-1 сек загрузки. Я не раз уже говорил, что чанк — более тяжелый объект, чем сниппет. Пока не научишься не использовать чанки там, где этого не требуется, на хорошую оптимизацию даже не надейся.
avatar
А я не знаю, чем именно диктуется аыбор чанка или сниппета — на фскн сниппетом сделал, на медцентре — чанком)))
avatar
Но в данном примере у тебя вообще и сниппет, и чанк, и еще и проверка главная это страница, или нет. Я и говорю, что это получается слишком избыточное решение. Сама мысль использовать общий кеш для всех страниц, и использовать JS для определения пункта active — правильная, а реализация избыточная. Надо просто упростить все до одного сниппета и все.
avatar
Фиг знает почему, но я считаю неприемлемым для себя вариант с подстановкой класса js'ом. Уж лучше какнибудь str_replace'ом на стороне сервера…
avatar
Да нет, JS — вообще не зло. Почему не перенести часть нагрузки на клиент? Сейчас все равно без javascript практически ни один ресурс не делается. Здесь вот тоже ничего не написать, если JS отключен. А перенос логики .active на клиент — это хорошая компенсация той экономии нагрузки, что обеспечивается таким кешированием. На представленных ресурсах меню действительно большое, и каждый раз его формировать, это нехилая нагрузка. А если там несколько тысяч ресурсов? Игра стоит свеч.
avatar
Согласен полностью. Не пойму почему в эру электричества нужно бегать с факелами ожидая отключения!
avatar
Вот это не в бровь, а в глаз
avatar
Дык помойму сейчас больше половины сайтов в jQuery ) что уж говорить просто про js
если не ошибаюсь тех у кого отключен js меньше 1%)
и думаю неподсвеченное меню это мелочи) главное что б сайт вообще не рассылпался при js off
avatar
Предлагаю всем перейти на самый легкий браузер Lynx и начать верстать по него))
avatar
)) можно я как то буду ориентироваться на большенство пользователей)
те которые 99% )
avatar
Так и я всегда ориентировался на 99% )
avatar
Можно через XPath пометить все пункты от самого младшего
avatar
а что если так?
$("a[href='[[~[[*id]]]]']").parents("li").addClass("active");
avatar
Надо будет попробовать, спасибо)))
avatar
Так как Wayfinder может принимать различные параметры на формирование ссылки (от корня, абсолютные и т.п.), может оказаться вообще не круто вшивать УРЛ в JS для поиска потом родителей. Лучше в шаблон формирования меню сразу добавить ссылкам rel=«link_[[*id]]» или class=«link_[[*id]]» (да там много способов это воткнуть), и в js уже искать $('a.link_[[*id]]').parents(...;
avatar
Кстати, а ведь именно так и надо делать)))
avatar
Если принять в расчёт, что у ссылок есть класс вида «link-[[*id]]», то вот:
var tagLimit = 'nav#section'; // нужен, чтобы ограничить подъём по дереву DOM (вдруг у кого-то вёрстка на списках построена, а не только меню)
var className = 'active';
var setActiveMenuItem = function ($li) {
	var $liParent = $li.addClass(className).parent('ul').closest('li');
	if ($li.closest(tagLimit).length && $li.length) {
		setActiveMenuItem($liParent);
	}
}

setActiveMenuItem($(tagLimit +' .link-[[*id]]').closest('li'));


Рекурсивно вставляет нужный класс для списков любого уровня вложенности.

Если класс вида «link-[[*id]]» стоИт не у ссылок, а _элементов списка_ («li»), то функцию вызывайте вот так:
setActiveMenuItem($(tagLimit +' .link-[[*id]]'));


А если всё-таки искать по урлу, то вызов такой:
setActiveMenuItem($(tagLimit +' [href="http://community.modx-cms.ru/index/discussed/"]').closest('li'));
avatar
Большое спасибо. Добавляю свою собственную статью в избранное ради комментариев)))
avatar
можете сразу комментарии добавлять в избранное )
avatar
Блин, точно) Как-то не вглядывался в контроллы под коментами)))
avatar
не за что! вам спасибо за статью! :-)
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.