使用自定义沃克拆分wp_nav_menu


16

我正在尝试创建一个最多显示5个项目的菜单。如果还有更多项目,则应将它们包装到另一个<ul>Element中以创建一个下拉列表。

5件以下:

落下

6件以上

落下

我知道这种功能很容易用助行器创建,该助行器对菜单项进行计数,如果剩下的余数超过5个,则将其包装到单独的包装中<ul>。但是我不知道如何创建这个沃克。

当前显示我的菜单的代码如下:

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

我注意到,如果菜单不是由用户定义的,而是使用后备功能,则walker无效。我需要它在两种情况下都能正常工作。


1
自定义菜单浏览器是一个扩展的类,Walker_Nav_Menu并且在抄本中有一个示例。“我不知道如何创建Walker”是什么意思?
cybmeta 2015年

顺便说一句,+ 1,因为这个想法实际上很棒。您是怎么发现它的?您有信息来源或其他内容吗?如果是这样,我很乐意阅读。提前致谢。
kaiser 2015年

@kaiser只是一个讨厌的设计想法:)没有源帖子,这就是为什么我要问。
Snowball'3

@cybmeta我知道创建助行器,并且在抄本中有一个示例,但是没有针对此特定问题的示例。因此,我不知道如何创建可为我提供解决方案的自定义助行器
Snowball 2015年

您应该向UX.SE专家咨询有关此想法的信息,并验证用户是否有此想法。UX是一个非常棒的网站,它为可用性/经验带来了相当不错的现实检查,并定期提供周到的答案和问题。您也可以回来,我们一起来完善这个想法。(那真是太棒了!)。
kaiser 2015年

Answers:


9

使用自定义的Walker,该start_el()方法可以访问$depthparam:当它为0elemnt 时,它是顶级的,我们可以使用此信息来维护内部计数器。

当计数器达到极限时,我们可以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>')
));

作品很棒!Giuseppe的出色工作。最好的是,如果前五个菜单元素中都有一个子菜单,它也可以正常工作。如果不需要的话,它不会仅将单个菜单点包装到子菜单中。只是一个小的事情:在默认情况下它显示6个要素$split_at = 5,但$count指数从0开始
雪球

谢谢@Snowball我修复了那个小问题,现在菜单显示了作为$split_at参数传递的确切数字,默认为5。
gmazzap

10

甚至有一种方法可以单独使用CSS来实现。这有一些限制,但是我仍然认为这可能是一种有趣的方法:

局限性

  • 您需要对下拉菜单的宽度进行硬编码
  • 浏览器支持。您基本上需要CSS3选择器。但是,尽管我还没有测试过,但从IE8起的所有功能都应该可以使用。
  • 这更多是一个概念证明。有几个缺点,例如只有在没有子项目时才起作用。

方法

尽管我并没有真正使用“数量查询” :nth-child,但~我在最近的CSS数量查询中已经读到了它的创造性用法,这就是导致我采用此解决方案的原因。

该方法基本上是这样的:

  1. 隐藏第4个以后的所有项目
  2. ...使用before伪元素添加点。
  3. 将鼠标悬停在点(或任何隐藏的元素)上时,会显示额外的项目,也就是子菜单。

这是默认WordPress菜单标记的CSS代码。我已在网上发表评论。

/* 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/

如果您还有其他疑问,请发表评论,我们很乐意进一步澄清代码。


感谢您的方法。我已经考虑过使用CSS来做,但是我认为它更干净,可以直接在php中完成。另外,该解决方案将第5个菜单点置于子菜单中,如果只有五个菜单项,则也不需要。
2015年

好吧,仅针对5个以上的项目启用它可能会得到修复。无论如何,我知道这并不完美,PHP方法可能更干净。但是我仍然发现它很有趣,可以出于完整性考虑将其包括在内。另一个选择总是很好。:)
kraftner 2015年

2
当然。顺便说一句。如果您在此添加另一个子菜单,它也会中断
Snowball 2015年

1
当然。到目前为止,这更多还是一个概念证明。添加了警告。
卡夫特纳2015年

8

您可以使用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>';
}

1
我已经编辑了几个小问题,但是仅当所有菜单项都没有子项时,此方法才有效。因为正则表达式无法识别层次结构。测试它:如果前四个菜单项中的任何一个包含一个子项,则该菜单已被破坏。
gmazzap

1
确实如此。在那种情况下DOMDocument可以使用。但是,在此问题中没有子菜单,因此对于这种特定情况,答案是正确的。DOMDocument将是“通用”解决方案,但我现在没有时间。您可以调查;)遍历LI项,如果有UL子级跳过它,那将是解决方案,但需要书面版本:)
mjakic 2015年

1
(a)您不知道OP中是否有子菜单。子菜单在鼠标悬停时显示,因此... (b)是,DOMDocument可能有效,但是在这种情况下,您需要递归循环项目以检查inner ul。WordPress已经在菜单浏览器中循环显示菜单项。就其本身而言,这已经是一个缓慢的操作,添加和附加循环我认为不是正确的解决方案,在相反的情况下,定制的助行器将是更干净,有效的解决方案。
gmazzap

谢谢大家,但是@gmazzap是正确的,其他菜单点(前四个或其他菜单点)可能包含另一个子菜单。所以这种灵魂是行不通的。
2015年

您还可以放置两个菜单,主菜单和“隐藏”菜单。添加带有三个点“ ...”的样式按钮,并在单击或悬停时显示第二个菜单。应该超级容易。
mjakic'3

5

有工作功能,但不确定是否是最佳解决方案。

我使用了自定义的助行器:

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>-tags。在定制的助行器中也有可能做到这一点,但是我没有找到位置和方式。

两个问题: 1.如果菜单中只有5个项目,则它会将最后一个项目也包装到一个尽管没有必要的项目中。

  1. 如果用户实际上已经为theme_location分配了一个菜单,它就会起作用,如果wp_nav_menu显示后备功能,则walker不会触发

您是否尝试过前四个项目中的任何一个具有某些子菜单会怎样?提示:substr_count($output,'<li')== 4放错位置...
gmazzap
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.