it-swarm.dev

Разделить wp_nav_menu с пользовательским Уокером

Я пытаюсь создать меню, которое показывает максимум 5 пунктов. Если есть еще элементы, следует поместить их в другой элемент <ul>, чтобы создать раскрывающийся список.

5 предметов или меньше:

Dropdown

6 предметов или больше

Dropdown

Я знаю, что такую ​​функциональность можно легко создать с помощью устройства, которое подсчитывает пункты меню и переносит их, если их больше, чем 5, в отдельный <ul>. Но я не знаю, как создать этот ходок.

Код, который показывает мое меню на данный момент, выглядит следующим образом:

<?php wp_nav_menu( array( 'theme_location' => 'navigation', 'fallback_cb' => 'custom_menu', 'walker' =>new Custom_Walker_Nav_Menu ) ); ?>

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

15
Snowball

Используя пользовательский Walker, метод start_el() имеет доступ к параметру $depth: когда это 0, элемент является главным, и мы можем использовать эту информацию для поддержания внутреннего счетчика.

Когда счетчик достигнет предела, мы можем использовать DOMDocument, чтобы получить из полного вывода HTML только последний добавленный элемент, обернуть его в подменю и снова добавить в HTML.


Правка

Когда количество элементов в точности соответствует количеству, которое нам требуется + 1, например, нам нужно, чтобы 5 элементов были видны, а в меню 6, нет смысла разбивать меню, потому что элементов будет 6 в любом случае. Код был отредактирован для решения этой проблемы.


Вот код:

class SplitMenuWalker extends Walker_Nav_Menu {

  private $split_at;
  private $button;
  private $count = 0;
  private $wrappedOutput;
  private $replaceTarget;
  private $wrapped = false;
  private $toSplit = false;

  public function __construct($split_at = 5, $button = '<a href="#">&hellip;</a>') {
      $this->split_at = $split_at;
      $this->button = $button;
  }

  public function walk($elements, $max_depth) {
      $args = array_slice(func_get_args(), 2);
      $output = parent::walk($elements, $max_depth, reset($args));
      return $this->toSplit ? $output.'</ul></li>' : $output;
  }

  public function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0 ) {
      $this->count += $depth === 0 ? 1 : 0;
      parent::start_el($output, $item, $depth, $args, $id);
      if (($this->count === $this->split_at) && ! $this->wrapped) {
          // split at number has been reached generate and store wrapped output
          $this->wrapped = true;
          $this->replaceTarget = $output;
          $this->wrappedOutput = $this->wrappedOutput($output);
      } elseif(($this->count === $this->split_at + 1) && ! $this->toSplit) {
          // split at number has been exceeded, replace regular with wrapped output
          $this->toSplit = true;
          $output = str_replace($this->replaceTarget, $this->wrappedOutput, $output);
      }
   }

   private function wrappedOutput($output) {
       $dom = new DOMDocument;
       $dom->loadHTML($output.'</li>');
       $lis = $dom->getElementsByTagName('li');
       $last = trim(substr($dom->saveHTML($lis->item($lis->length-1)), 0, -5));
       // remove last li
       $wrappedOutput = substr(trim($output), 0, -1 * strlen($last));
       $classes = array(
         'menu-item',
         'menu-item-type-custom',
         'menu-item-object-custom',
         'menu-item-has-children',
         'menu-item-split-wrapper'
       );
       // add wrap li element
       $wrappedOutput .= '<li class="'.implode(' ', $classes).'">';
       // add the "more" link
       $wrappedOutput .= $this->button;
       // add the last item wrapped in a submenu and return
       return $wrappedOutput . '<ul class="sub-menu">'. $last;
   }
}

Использование довольно просто:

// by default make visible 5 elements
wp_nav_menu(array('menu' => 'my_menu', 'walker' => new SplitMenuWalker()));

// let's make visible 2 elements
wp_nav_menu(array('menu' => 'another_menu', 'walker' => new SplitMenuWalker(2)));

// customize the link to click/over to see wrapped items
wp_nav_menu(array(
  'menu' => 'another_menu',
  'walker' => new SplitMenuWalker(5, '<a href="#">more...</a>')
));
9
gmazzap

Есть даже способ сделать это возможным только с помощью CSS. Это имеет некоторые ограничения, но я все же подумал, что это может быть интересный подход:

Ограничения

  • Вам нужно жестко указать ширину выпадающего
  • Браузер-поддержка. Вам в основном нужны CSS3 селекторы . Но все, начиная с IE8 и выше, должно работать, хотя я этого не проверял.
  • Это скорее подтверждение концепции. Есть несколько недостатков, например, работа только при отсутствии подпунктов.

Подход

Хотя я на самом деле не использую "Количество запросов", креативное использование :nth-child и ~, которое я читал в недавних Количество запросах для CSS , привело меня к этому решению.

Подход в основном такой:

  1. Скрыть все предметы после 4-го
  2. Добавьте точки ..., используя псевдоэлемент before.
  3. При наведении курсора на точки (или любые скрытые элементы) показывайте дополнительные элементы, такие как подменю.

Вот код CSS для разметки меню WordPress по умолчанию. Я прокомментировал встроенный.

/* Optional: Center the navigation */
.main-navigation {
    text-align: center;
}

.menu-main-menu-container {
    display: inline-block;
}

/* Float menu items */
.nav-menu li {
    float:left;
    list-style-type: none;
}

/* Pull the 5th menu item to the left a bit so that there isn't too
   much space between item 4 and ... */
.nav-menu li:nth-child(4) {
    margin-right: -60px;
}

/* Create a pseudo element for ... and force line break afterwards
   (Hint: Use a symbol font to improve styling) */
.nav-menu li:nth-child(5):before {
    content: "...\A";
    white-space: pre;
}

/* Give the first 4 items some padding and Push them in front of the submenu */
.nav-menu li:nth-child(-n+4) {
    padding-right: 15px;
    position: relative;
    z-index: 1;
}

/* Float dropdown-items to the right. Hardcode width of dropdown. */
.nav-menu li:nth-child(n+5) {
    float:right;
    clear: right;
    width: 150px;
}

/* Float Links in dropdown to the right and hide by default */
.nav-menu li:nth-child(n+5) a{
    display: none;      
    float: right;
    clear: right;
}   

/* When hovering the menu, show all menu items from the 5th on */
.nav-menu:hover li:nth-child(n+5) a,
.nav-menu:hover li:nth-child(n+5) ~ li a{
    display: inherit;
}

/* When hovering one of the first 4 items, hide all items after it 
   so we do not activate the dropdown on the first 4 items */
.nav-menu li:nth-child(-n+4):hover ~ li:nth-child(n+5) a{
    display: none;
}

Я также создал jsfiddle, чтобы показать его в действии: http://jsfiddle.net/jg6pLfd1/

Если у вас есть дополнительные вопросы, как это работает, пожалуйста, оставьте комментарий, я был бы рад уточнить код дальше.

10
kraftner

Вы можете использовать фильтр wp_nav_menu_items. Он принимает вывод меню и аргументы, которые содержат атрибуты меню, такие как кусок меню, контейнер и т.д.

add_filter('wp_nav_menu_items', 'wpse_180221_nav_menu_items', 20, 2);

function wpse_180221_nav_menu_items($items, $args) {
    if ($args->menu != 'my-menu-slug') {
        return $items;
    }

    // extract all <li></li> elements from menu output
    preg_match_all('/<li[^>]*>.*?<\/li>/iU', $items, $matches);

    // if menu has less the 5 items, just do nothing
    if (! isset($matches[0][5])) {
        return $items;
    }

    // add <ul> after 5th item (can be any number - can use e.g. site-wide variable)
    $matches[0][5] = '<li class="menu-item menu-item-type-custom">&hellip;<ul>'
          . $matches[0][5];

    // $matches contain multidimensional array
    // first (and only) item is found matches array
    return implode('', $matches[0]) . '</ul></li>';
}
8
mjakic

Есть рабочая функция, но не уверен, что это лучшее решение.

Я использовал нестандартный ходок:

class Custom_Walker_Nav_Menu extends Walker_Nav_Menu {
function start_el(  &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
    global $wp_query;
    $indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';

    $classes = empty( $item->classes ) ? array() : (array) $item->classes;
    $classes[] = 'menu-item-' . $item->ID;

    $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );
    $class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';

    $id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args, $depth );
    $id = $id ? ' id="' . esc_attr( $id ) . '"' : '';

    /**
     * This counts the $menu_items and wraps if there are more then 5 items the
     * remaining items into an extra <ul>
     */
    global $menu_items;
    $menu_items = substr_count($output,'<li');
    if ($menu_items == 4) {
      $output .= '<li class="tooltip"><span>...</span><ul class="tooltip-menu">';
    }

    $output .= $indent . '<li' . $id . $class_names .'>';

    $atts = array();
    $atts['title']  = ! empty( $item->attr_title ) ? $item->attr_title : '';
    $atts['target'] = ! empty( $item->target )     ? $item->target     : '';
    $atts['rel']    = ! empty( $item->xfn )        ? $item->xfn        : '';
    $atts['href']   = ! empty( $item->url )        ? $item->url        : '';

    $atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth );

    $attributes = '';
    foreach ( $atts as $attr => $value ) {
      if ( ! empty( $value ) ) {
        $value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
        $attributes .= ' ' . $attr . '="' . $value . '"';
      }
    }

    $item_output = $args->before;
    $item_output .= '<a'. $attributes .'>';
    $item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
    $item_output .= '</a>';
    $item_output .= $args->after;

    $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );

  }
}

Функция, которая показывает фактическое меню, следующая:

        <?php
        wp_nav_menu( array( 'container' => false, 'theme_location' => 'navigation', 'fallback_cb' => 'custom_menu', 'walker' =>new Custom_Walker_Nav_Menu ) );
        global $menu_items;
        // This adds the closing </li> and </ul> if there are more then 4 items in the menu
        if ($menu_items > 4) {
            echo "</li></ul>";
        }
        ?>

Я объявил глобальную переменную $ menu_items и использовал ее для отображения закрывающих тегов <li> и <ul>-. Вероятно, это можно сделать и в обычном ходунке, но я не нашел, где и как.

Две проблемы: 1. Если в меню всего 5 пунктов, последний элемент также включается в альт-мысль, в которой он не нужен.

  1. Это просто работает, если пользователь фактически выделил меню для theme_location, ходок не срабатывает, если wp_nav_menu показывает функцию возврата
5
Snowball