WordPress - 如何知道菜单项是否有子菜单?

31

我正在开发一个带有嵌套子菜单的Wordpress主题。我需要将没有子元素的元素在视觉上与具有子元素的元素区分开来。现在我有这个菜单,但这可能会改变:

A
  a1
  a2
B
  b1
  b2
C

如您所见,A和B有子元素,而C没有——我需要在CSS级别上做出不同。

理想情况下,我希望A和B中有一个has-children类,但C没有。

到目前为止,我已经成功创建了一个"菜单 Walker" PHP 类,我可以实例化并传递给wp_nav_menu 。它的构造函数如下:

class My_Walker_Nav_Menu extends Walker_Nav_Menu {
  function start_el(&$output, $item, $depth, $args) {
    ...
    if(??? $item has children???) {
      // I know what to do here
    }
  }
}

那么,我该如何判断$item是否有子项或者是一个叶节点?

编辑:这个问题在WordPress论坛中被一个名叫“keesiemeijer”的人回答了。我将保留此赏金,以防他想要重新认领它。否则,我将标记自己的答案为有效。

14个回答

34

将此代码添加到functions.php,它将为父元素添加“下拉菜单”类。

新方法性能更好

function menu_set_dropdown( $sorted_menu_items, $args ) {
    $last_top = 0;
    foreach ( $sorted_menu_items as $key => $obj ) {
        // it is a top lv item?
        if ( 0 == $obj->menu_item_parent ) {
            // set the key of the parent
            $last_top = $key;
        } else {
            $sorted_menu_items[$last_top]->classes['dropdown'] = 'dropdown';
        }
    }
    return $sorted_menu_items;
}
add_filter( 'wp_nav_menu_objects', 'menu_set_dropdown', 10, 2 );

现在:对数据库的密集操作

add_filter( 'nav_menu_css_class', 'check_for_submenu', 10, 2);
function check_for_submenu($classes, $item) {
    global $wpdb;
    $has_children = $wpdb->get_var("SELECT COUNT(meta_id) FROM wp_postmeta WHERE meta_key='_menu_item_menu_item_parent' AND meta_value='".$item->ID."'");
    if ($has_children > 0) array_push($classes,'dropdown'); // add the class dropdown to the current list
    return $classes;
}

2
请注意,此方法会为每个菜单项添加另一个数据库查询,如果您有很多菜单项,可能会减慢您的网站速度! - Ahrengot
@Ahrengot 看看我的新方法,对于数据库来说要好得多 ;) - janw
2
它在 WP 3.6 中没有影响?还需要添加或删除其他内容吗?我正在使用 wp_nav_menu()。 - rashid
@RashidShafique 它在3.6中有效。您是否在wp-admin中分配了菜单?我在干净的安装中使用了twentyeleven和没有其他插件进行了测试。 - janw
很好的解决方案,但是有人知道为什么它在子菜单中不起作用吗?(一个下拉菜单中的下拉菜单) - radu.luchian

25

使用方法很简单:

解释: 我使用"waker"创建菜单:

$walker = new Nav_Walker;
wp_nav_menu(array(
        'container'=>'nav',
        'container_class'=>'menu',
        'menu_class'=>'list-unstyled list-inline',
        'walker'=>$walker
    ));

类 Walker:

class Nav_Walker extends Walker_Nav_Menu
{ 
      public function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0)
    {
        if($args->walker->has_children)
        {
            //code...
        }   
    }
}

我们有一个名为'walker'的对象,你可以var_dump($args)来查看更多信息。我在我的项目中使用它!


4
欢迎来到 Stack Overflow,starrynight,并感谢您的第一个答案。为了提高您的回答得分,建议您在代码中包含解释为什么会解决问题的说明。请注意不要改变原意,尽量使翻译通俗易懂。 - nicolaus-hee

14

看起来这个问题终于得到了解决。截至目前撰写本文的最新WordPress beta版本为4.0,已更新Walker_Nav_Menu类并添加了$has_children属性。

/**
 * Whether the current element has children or not.
 *
 * To be used in start_el().
 *
 * @since 4.0.0
 * @access protected
 * @var bool
 */
protected $has_children;

所以,我们不再需要黑客攻击 function display_element(...) 了。


是的,现在它已经自动添加了。 - Salem

8

我在WordPress论坛上提问,keesiemeijer指向了这篇文章,其中他们做了以下操作:

不是修改start_el,而是修改display_element,添加了以下两行代码(37-38行 在此处):

//display this element (THESE ARE NOT THE LINES)
if ( is_array( $args[0] ) )
  $args[0]['has_children'] = ! empty( $children_elements[$element->$id_field] );

// THESE TWO ARE THE LINES:               
if( ! empty( $children_elements[$element->$id_field] ) )
  array_push($element->classes,'parent');

我已将前两行作为特殊参考留下,并作为对本帖其他答案的评论。似乎wordpress正在“尝试”在$args中设置一个´has_children´属性,但它要么做错了,要么以一种我不理解的方式进行。无论如何,该has_children参数从未传递到start_el(请参见$args的样本var_dump here)。
这可能是我使用的Wordpress版本(3.2.1)上的错误,可能已在最新版本中修复。
无论如何,我在Wordpress论坛上得到的答案帮助我解决了这个问题,所以我认为这个问题已经解决了。我会等待赏金过期,以防keesiemeijer想在此处发布他的答案。

(1) 有趣的是,这个问题在 WP 的代码库中已经存在了相当长的时间(截至当前写作时,在 WP 3.5.1 中行为没有改变)仍未得到解决。 (2) 问题在于 $args[0] 实际上是一个对象。我可能会提交一个补丁;它应该被处理,假定使用 elseif (is_object($args[0])) { $args[0]->has_children = ... } - Chris Krycho
而且,稍微挖掘一下就会发现有人已经提交了那个补丁(请参见这里)-由于某种原因,它还没有被推入核心。 - Chris Krycho
为了其他遇到同样问题的人,我已经添加了另一个答案,链接如下:https://dev59.com/3Goy5IYBdhLWcg3wl_Ex#16084680。但是,我会引用你的答案,因为如果没有你的回答,我会很难弄清楚问题所在。 - Chris Krycho

6
class My_Walker_Nav_Menu extends Walker_Nav_Menu {
  function start_el(&$output, $item, $depth, $args) {
    ...
    if($args['has_children']) {
      // I know what to do here
    }
  }
}

2
很遗憾,那个方法不会起作用。有两个原因——首先,$args是一个stdObject,而不是一个array,所以语法应该是$args->has_children。其次,$args没有has_children属性(我使用var_dump($args)进行了检查)。无论是它还是$item似乎都没有这样的属性。 - kikito
@kikito,你能发布一下var_dump($args)的结果吗? - Eugene Manuilov
@kikito 奇怪,在我的程序里面有“has_children”参数。 - Eugene Manuilov
11
如果($args->walker->has_children) - Steven Vachon

4

Kikito的上面的答案可以完成操作,但不够灵活。我认为更好的方法是这样的:

function display_element($element, &$children_elements, $max_depth, $depth=0, $args, &$output) {
    // the first few lines of the method...

    //display this element; handle either arrays or objects gracefully
    if ( is_array( $args[0] ) )
        $args[0]['has_children'] = ! empty( $children_elements[$element->$id_field] );

    elseif ( is_object($args[0]) )
        $args[0]->has_children =  ! empty( $children_elements[$element->$id_field] );

    // the rest of the method...
}

覆盖 Walker::display_element() 是正确的做法,但最好实际解决问题而不是仅仅在此处添加一个类,原因有两个。首先,真正的问题不是缺少类,而是WordPress中未修补的错误,正如Kikito所指出的:问题在于$args[0]并不总是一个数组。这似乎是Walker::display_element()通常期望的类型,但我们实际上在处理Walker_Nav_Menu::display_element(),在这种情况下,args以标准对象类型而不是数组类型传递。因此,我们只需要使用对象符号而不是数组符号添加 has_children元素即可。问题解决了![1]
添加那个elseif可以处理普通导航菜单的情况。这与希望进入核心类的此补丁相同,到那时,您将不再需要扩展它。他们可能会进一步修补程序以解决$args[0]既不是数组也不是对象的情况,但我不认为会发生这种情况。
其次,为了保持各种方法和类之间的良好分离,应该在start_el()方法或其他地方添加类,因为display_element()并没有处理任何类的操作。
因此,您可以随意覆盖start_el():您可以添加自己的自定义类,或者完全忽略元素,或者提供自定义文本,或者任何您喜欢的内容。(在我的情况下,我正在解决一个现有的Javascript菜单实现,它基于父项和子项具有非常特定的类要求,因此我不能仅将相同的类添加到每个具有子项的元素中 - 这正是为什么这种关注点分离很重要的原因。)在我的代码中:
function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
    $indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';
    $class_names = $value = '';

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

    $has_children = (is_object($args) && $args->has_children) || (is_array($args) &&  $args['has_children']);
    if ($has_children) {
        // do whatever you need to do
    }

    // everything else the method does...
}

[1] 当然,这是像PHP这样的动态类型语言可能存在的潜在陷阱之一...只要你小心,这不是问题。WordPress开发人员在这里没有小心。


4
请将以下内容添加到您的functions.php文件中:
add_filter('wp_nav_menu_objects', 'menu_has_children', 10, 2);

function menu_has_children($sorted_menu_items, $args) {
    $parents = array();
    foreach ( $sorted_menu_items as $key => $obj )
            $parents[] = $obj->menu_item_parent;
    foreach ($sorted_menu_items as $key => $obj)
        $sorted_menu_items[$key]->has_children = (in_array($obj->ID, $parents)) ? true : false;
    return $sorted_menu_items;
}

然后在你的遍历函数中,你可以检查$item->has_children是否为真或假。


1
这是最好的答案。 - Daniel Harris

3

由于这个问题是谷歌搜索结果中最早的之一,已被其他网页引用,并且这里的大多数答案都已过时,因此我想发布这个答案。

start_el() 函数有 classes 作为 $item 对象的一部分。因此,您可以在 start_el() 中添加以下内容:

   if(in_array('menu-item-has-children', $item->classes) && $depth != 0)
   {
       // Your Code
   }

请注意,条件中不需要$depth,但如果移除,则您的代码将应用于第一个项目(即深度为0的项目)。
至于兼容性部分,类'menu-item-has-children'已添加到WordPress 3.7(2013年10月),我已在最新的WordPress 4.4上进行了测试(截至发布此答案的时间)。

在这个例子中,$depth是如何设置的? - Floris
它由start_el提供:https://developer.wordpress.org/reference/classes/walker_nav_menu/start_el/ - Kalimah

3
如果您不想使用繁琐的查询或函数,您可以在jQuery中执行以下操作:
(function() {
    // Add 'has_children' class to menus
    jQuery.each(jQuery('.menu-item').has('ul.sub-menu'), function() {
        jQuery(this).addClass('has_children');
    });
})();

一个服务器端的PHP函数几乎肯定比前端的jQuery调用添加更少的“开销”,特别是如果你考虑缓存的话。此外,这个解决方案(1)假设jQuery已经在使用中,(2)做这样的事情不会干扰后端任何其他菜单相关的JS。 - Chris Krycho
2
@ChrisKrycho jQuery已经与WordPress一起加载。添加新类对后端没有影响。在高负载服务器上,最好避免另一个查询。当然,这只是决定使用的一个解决方案。 - Zachary Schuessler
1
啊,说得好,我忘记了这一点(因为我总是拉取已发布的版本并推送自己的版本,因为已发布的版本几乎从来不是最新的)。 - Chris Krycho

3
    /**
     * @see My_Nav_Walk::start_el()
     * @since 3.0.0
     *
     * @param string $output Passed by reference. Used to append additional content.
     * @param object $item Menu item data object.
     * @param int $depth Depth of menu item. Used for padding.
     * @param int $current_page Menu item ID.
     * @param object $args
     * @url:http://www.liyinqing.com
     */
class My_Nav_Walk extends Walker_Nav_Menu {
    function start_el(&$output, $item, $depth, $args) {
    global $wp_query;
    $indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';

    $class_names = $value = '';

    $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 ) );
    $class_names = ' class="' . esc_attr( $class_names ) . '"';

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

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

    $attributes  = ! empty( $item->attr_title ) ? ' title="'  . esc_attr( $item->attr_title ) .'"' : '';
    $attributes .= ! empty( $item->target )     ? ' target="' . esc_attr( $item->target     ) .'"' : '';
    $attributes .= ! empty( $item->xfn )        ? ' rel="'    . esc_attr( $item->xfn        ) .'"' : '';
    $attributes .= ! empty( $item->url )        ? ' href="'   . esc_attr( $item->url        ) .'"' : '';

    // Check our custom has_children property.here is the points
    if ( $args->has_children ) {
      $attributes .= ' class="menu parent"';
    }

    $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 );
  }

  function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {
    $id_field = $this->db_fields['id'];
    if ( is_object( $args[0] ) ) {/.here is the points
      $args[0]->has_children = ! empty( $children_elements[$element->$id_field] );
    }
    return parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
  }

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接