Вывод подменю отдельно от основного меню в 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 для работы с блоками навигации.

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

  1. Михаил

    Спасибо, Олег, за данный метод вывода.
    Я для себя пробовал аналогичное реализовать с помощью walker для wp_nav_menu.
    Но, что в моем методе, что в вашем есть проблема.
    А именно: не отображается подменю на странице записи.
    А хотелось бы, чтобы на странице записи выводились полностью все подрубрики (если запись есть в одной из них).
    В итоге я сделал основное меню через wp_nav_menu, а вот все подменю (у меня они такие же, как подрубрики) выводятся wp_list_categories.
    Хотелось бы, конечно, чтобы управление всем этим находилось в одном месте, а именно в wp_nav_menu.
    Можно ли ваш метод приспособить под вывод всех пунктов подменю, если мы находимся в записи?

    • Не совсем понял, какая у вас структура меню. Но если нечто подобное:

      > Родительская рубрика
          > Вложенная рубрика 1
          > Вложенная рубрика 2
          > Вложенная рубрика 3
      

      и мы находимся на странице записи, которая находится в рубрике 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.
        Надеюсь, что понятно объяснил :)

  2. Ольга

    Спасибо большое, похоже, это то, что мне нужно. Только я так и не поняла, как эту функцию вызывать.
    Можно объяснить для чайников — что нужно написать на странице шаблона, чтобы эта функция заработала?

    • Пример вызова указан в посте. Чтобы он был чуть более очевидным, я его отредактировал. Можно вставлять в шаблон просто копируя.

  3. Илья

    Да, статья полезная!

  4. Игорь

    Отличное решение! Спасибо! Прямо что и искал. А то уже замучился с этим меню.

  5. Игорь

    И да, сайт в избранное. =)

  6. Андрей

    Спасибище за функцию!!!!!!

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *