Wordpress - 我怎么知道菜单项是否有孩子?

时间:2011-12-09 16:58:38

标签: php wordpress

我正在使用嵌套子菜单开发一个wordpress主题。我需要让没有孩子的元素与有孩子的元素在视觉上不同。现在我有这个菜单,但这可能会改变:

A
  a1
  a2
B
  b1
  b2
C

如你所见,A和B有孩子。 C没有 - 我需要它在CSS级别上有所不同。

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

到目前为止,我已经设法创建了一个“Menu 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 个答案:

答案 0 :(得分:32)

将此添加到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;
}

答案 1 :(得分:14)

使用这种方式很简单:

说明: 我用“walker”创建菜单:

$walker = new Nav_Walker;
wp_nav_menu(array(
        'container'=>'nav',
        'container_class'=>'menu',
        'menu_class'=>'list-unstyled list-inline',
        '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)来查看更多内容。 我正在使用我的项目!

答案 2 :(得分:9)

似乎问题终于得到了解决。截至当前写作4.0的最新Wordpress测试版更新了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(...)

答案 3 :(得分:6)

我在WordPress forum询问,keesiemeijer向我指出this other post,其中他们做了以下事情:

他们修改start_el而不是修改display_element,而是添加以下两行(第37-38行here):

//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 here的示例var_dump)

这可能是我所获得的Wordpress版本(3.2.1)的错误,可能已在最新版本中修复。

无论如何,我在Wordpress论坛上得到的答案是帮助我解决它的答案,所以我认为这已经解决了。如果keesiemeijer想在这里提出他的答案,我会等待赏金到期。

答案 4 :(得分:3)

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
    }
  }
}

答案 5 :(得分:3)

Kikito的answer above完成了诀窍,但并非以最可重复使用的方式完成。在我看来,更好的方法是这样的:

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()是正确的举措,但最好在问题的根源上实际解决问题,而不是简单地在此处修改课程,原因有两个。首先,真正的问题不是缺少类,而是Kikito指出的WordPress中未修补的错误:问题是$args[0]并不总是数组。这似乎是Walker::display_element()的典型预期类型,但我们实际上是在这里处理Walker_Nav_Menu::display_element(),在这种情况下,args最终作为标准对象类型传入而不是数组类型。因此,我们只需要使用对象表示法而不是数组表示法添加has_children元素。问题解决了![1]

为普通的Nav Menu案例添加elseif个帐户。这是同样的形式,希望它成为this patch中的核心类,此时你将不再需要扩展它。他们应该进一步补丁,以解释$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开发人员在这里并不小心。

答案 6 :(得分:2)

如果您不想要硬查询或函数的开销,可以在jQuery中执行此操作:

(function() {
    // Add 'has_children' class to menus
    jQuery.each(jQuery('.menu-item').has('ul.sub-menu'), function() {
        jQuery(this).addClass('has_children');
    });
})();

答案 7 :(得分:2)

将此添加到您的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是真还是假

答案 8 :(得分:1)

    /**
     * @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 );
  }

}

答案 9 :(得分:0)

感谢Start_el函数,我的函数遵循该函数来运行查询。 我有一个函数将通过ID计算父菜单的子菜单。

function nav_count_children($parent_id){
    global $wpdb;
    $query = "SELECT COUNT(*) FROM $wpdb->postmeta 
            WHERE meta_key='_menu_item_menu_item_parent' 
            AND meta_value=$parent_id";
    $count_children = $wpdb->get_var( $query );
    return $count_children;
}

生成 在wp_get_nav_menu_items函数的foreach中,通过$ item-&gt; menu_item_parent == 0选择父菜单的ID。

这对我很有用,非常简单。

答案 10 :(得分:0)

在你的步行者课程中使用这个简单的代码

 class Description_Walker extends Walker_Nav_Menu
    {
    function start_el(  &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
    global $wp_query;
    ////

    ur code in this part 
    ///////
     $depth_classes = array(
        ( $depth == 0 ? 'nav-item' : 'nav-submenu-item' ),
        ( $depth >=2 ? 'sub-sub-menu-item' : '' ),
        ( $depth % 2 ? 'menu-item-odd' : 'menu-item-even' ),
        'menu-item-depth-' . $depth
    );
    $depth_class_names = esc_attr( implode( ' ', $depth_classes ) );

    // passed classes
    $classes = empty( $item->classes ) ? array() : (array) $item->classes;
    $class_names = esc_attr( implode( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item ) ) );


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

答案 11 :(得分:0)

由于这个问题是谷歌搜索的第一个结果之一,已在其他网页中被引用,这里的大部分答案已经过时,我想我会发布这个答案。

start_el()作为 $ item 对象的一部分。所以你可以在start_el()中添加它:

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

请注意,条件中不需要 $ depth ,但如果删除,您的代码将应用于第一项(即深度为0的项目)。

至于兼容性部分,&#39; menu-item-has-children&#39; 已添加到WordPress 3.7 (October 2013),我已在最新的WordPress上测试过它4.4(截至发布此答案时)。

答案 12 :(得分:0)

我们不用重写Walker_Nav_MenuWalker类方法的核心功能,而只是使用良好的旧类继承。

代码

class Has_Child_Walker_Nav_Menu extends Walker_Nav_Menu {
    public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) {
        if ( ! $element ) {
            return;
        }
        $element->has_children = ! empty( $children_elements[ $element->{$this->db_fields['id']} ] );
        parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
    }
}

工作原理

  1. 我们创建了一个新类Has_Child_Walker_Nav_Menu,该类继承自Walker_Nav_Menu,而该类又继承自Walker
  2. 我们用自己的函数覆盖Walker->display_element()方法,因为它是我们在遍历菜单项树时第一个方法针对每个单独的菜单项。
  3. 在我们的display_element()方法中,我们在has_children a.k.a.菜单项上创建了一个新属性$element。这使得该属性在$itemstart_el()方法中的end_el()对象上都可用,这意味着它也传递给了start_el()所调用的过滤器。
  4. 我们新的has_children属性值的值的计算方法与确定Walker实例属性has_children的方法相同。
  5. 最后,我们在父display_element()上调用了Walker_Nav_Menu方法……实际上是祖父母Walker,因为Walker_Nav_Menu没有它自己的display_element()方法。我们确保将所有值从display_element()方法传递到Walker->display_element()方法。

就是这样!

现在我们有$item->has_children可用的任何地方,包括过滤器。

用法示例

菜单

wp_nav_menu(
    array(
        'theme_location'  => 'main_nav',
        'container_class' => 'main-nav',
        'walker'          => new Has_Child_Walker_Nav_Menu()
    )
);

过滤器

function my_menu_dropdown( $output, $item, $depth, $args ) {
    if ( $item->has_children ) {
        $output .= '<a href="#" class="expand-menu-toggle"><i class="fal fa-angle-down"></i></a>';
    }
    return $output;
}
add_filter( 'walker_nav_menu_start_el', 'my_menu_dropdown', 10, 4 );

答案 13 :(得分:-1)

有一个简单的解决方案source

function start_el(&$output, $item, $depth=0, $args=array()) {

  global $wpdb;
  $children_count = $wpdb->get_var(
    $wpdb->prepare("
      SELECT COUNT(*) FROM $wpdb->postmeta
      WHERE meta_key = %s
      AND meta_value = %d
    ", '_menu_item_menu_item_parent', $item->ID)
  );

  if( $children_count > 0 ) {
    // has children
    $classes[] = 'parent';
  }

  [...]