Вывод подменю отдельно от основного меню в WordPress
Я никогда не любил Joomla! ввиду многих причин, но одно мне нравилось в ней всегда – возможность отделить подменю от основного меню без каких-либо костылей и хардкодинга, используя нативный API. То есть мы можем отобразить подменю не привычным выпадающим списком, а выделить его в отдельный самостоятельный блок, с дальнейшим размещением в любом месте шаблона. Я не знаю, осталась ли эта возможность в текущих версиях данной CMS или, быть может, по каким-то причинам была упразднена. Но на заре своей деятельности фрилансера я использовал ее в двух проектах, где эти манипуляции с меню были очень востребованы (первый, второй).
Задача достаточно редкая, но время от времени ее приходится решать. Очередной проект с разделением меню и подменю заставил меня написать относительно универсальную функцию для этих целей.
Код функции:
<?php
/**
* The separate sub-menu
*
* Optional $args contents:
*
* container - Whether to wrap the ul, and what to wrap it with. Defaults to 'nav'.
* container_id - The ID that is applied to the container. Defaults to blank.
* container_class - the class that is applied to the container. Defaults to 'sub-menu-container'.
* submenu_class - CSS class to use for the ul element which forms the menu. Defaults to 'sub-menu'.
* string xpath - xPath expression.
* echo - Whether to echo the menu or return it. Defaults to echo.
*
* @author Oleg Murashov <o.murashov@gmail.com>
* @link http://omurashov.ru/wordpress/separate-output-menu-and-submenu/ Documentation
* @param array $args Arguments
* @return string|void.
*/
function get_submenu($args) {
$defaults = array(
'container' => 'nav',
'container_id' => '',
'container_class' => 'sub-menu-container',
'submenu_class' => 'sub-menu',
'submenu_id' => '',
'xpath' => "./li[contains(@class,'current-menu-item') or contains(@class,'current-menu-ancestor')]/ul",
'theme_location' => '',
'echo' => true
);
$args = wp_parse_args( $args, $defaults );
$args = (object) $args;
$menu = wp_nav_menu(
array(
'theme_location' => $args->theme_location,
'container' => '',
'echo' => false
)
);
$menu = simplexml_load_string($menu);
$submenu = $menu->xpath($args->xpath);
if (empty($submenu)) {
return;
}
// Set value of class attribute
$submenu[0]['class'] = $args->submenu_class;
// Add "id" attribute
if ($args->submenu_id) {
$submenu[0]->addAttribute('id', $args->submenu_id);
}
if ($args->container) {
$submenu_sxe = simplexml_load_string($submenu[0]->saveXML());
$sdm = dom_import_simplexml($submenu_sxe);
if ($sdm) {
$xmlDoc = new DOMDocument('1.0', 'utf-8');
$container = $xmlDoc->createElement($args->container);
// Add "class" attribute for container
if ($args->container_class) {
$container->setAttribute('class', $args->container_class);
}
// Add "id" attribute for container
if ($args->container_id) {
$container->setAttribute('id', $args->container_id);
}
$smsx = $xmlDoc->importNode($sdm, true);
$container->appendChild($smsx);
$xmlDoc->appendChild($container);
}
}
if (isset($xmlDoc)) {
$output = $xmlDoc->saveXML();
} else {
$output = $submenu[0]->saveXML();
}
if (!$args->echo) {
return $output;
}
echo $output;
}
?>
Работа с функцией очень похожа на работу с wp_nav_menu(). Параметры передаются в виде массива ключ-значение. К обязательным ключам можно условно отнести только theme_location, значение которого определяет основное меню, из которого мы будем выдергивать вложенные.
Ключ xpath позволяет определить поведение функции. По умолчанию, выбирается подменю для текущего активного пункта, если оно у него есть. Можно выбрать какое-то конкретное, передав строку вида ‘./li[contains(@class, «menu-item-99»)]/ul’.
Ключ container задает тэг контейнер, но в отличии от wp_nav_menu(), где его значение может быть только div или nav, здесь можно использовать любой тэг, например section или span.
Имена остальных ключей говорят сами за себя и в комментариях, я полагаю, не нуждаются.
Пример вызова
<?php
$submenu = get_submenu(array(
'container' => 'nav',
'container_id' => 'container-sub-menu',
'container_class' => 'container-sub-menu',
'submenu_id' => 'main-sub-menu',
'submenu_class' => 'sub-menu',
'theme_location' => 'header',
'echo' => false,
));
echo $submenu;
?>
Я думаю, аналогичную функцию можно написать для любой CMS, использующей вменяемый шаблонизатор и предлагающей минимальный API для работы с блоками навигации.
Комментарии (15)
Спасибо, Олег, за данный метод вывода.
Я для себя пробовал аналогичное реализовать с помощью walker для wp_nav_menu.
Но, что в моем методе, что в вашем есть проблема.
А именно: не отображается подменю на странице записи.
А хотелось бы, чтобы на странице записи выводились полностью все подрубрики (если запись есть в одной из них).
В итоге я сделал основное меню через wp_nav_menu, а вот все подменю (у меня они такие же, как подрубрики) выводятся wp_list_categories.
Хотелось бы, конечно, чтобы управление всем этим находилось в одном месте, а именно в wp_nav_menu.
Можно ли ваш метод приспособить под вывод всех пунктов подменю, если мы находимся в записи?
Не совсем понял, какая у вас структура меню. Но если нечто подобное:
и мы находимся на странице записи, которая находится в рубрике 1, 2 или 3, то моя функция будет работать, если передать ей параметр:
'xpath' => "./li[contains(@class,'current-menu-item') or contains(@class,'current-menu-ancestor') or contains(@class,'current-post-ancestor')]/ul"
Проблема в том, что запись находится только во Вложенной рубрике 2 (например).
Это и правильно. У меня например у Родительской рубрики красота, есть подрубрики Макияж и Народная медицина (например).
Относить пост ко всем подрубрикам было бы неправильно.
Я отношу пост, например только ко Вложенной рубрике 2 и хочу, чтобы в самом посте выводились все подрубрики Родительской рубрики.
А у меня выводится на странице записи только Вложенная рубрика 2.
Надеюсь, что понятно объяснил :)
Собственно, я это и имел ввиду — запись находится в рубрике 1 или 2, или 3 (в одной из). При таком раскладе функция работает правильно.
Спасибо большое, предложенный вариант прекрасно работает!
Наконец нашел то, что нужно.
Пользуйтесь на здоровье! :)
P.S: поправьте тайтл: «Раздельный вОДОд меню и подменю»
Действительно. Спасибо, исправил.
Спасибо большое, похоже, это то, что мне нужно. Только я так и не поняла, как эту функцию вызывать.
Можно объяснить для чайников — что нужно написать на странице шаблона, чтобы эта функция заработала?
Пример вызова указан в посте. Чтобы он был чуть более очевидным, я его отредактировал. Можно вставлять в шаблон просто копируя.
Да, статья полезная!
Отличное решение! Спасибо! Прямо что и искал. А то уже замучился с этим меню.
И да, сайт в избранное. =)
Спасибище за функцию!!!!!!
По умолчанию, если меню нет, то вместо него будут выведены страницы сайта. Но если нужно выводить меню, только в том случае когда оно создано в админ-панели, укажите параметр