php Wordpress - 我如何知道菜单项是否有子项?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8448978/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Wordpress - how do I know if a menu item has children?
提问by kikito
I'm developing a wordpress theme with nested submenus. I need to make the elements with no children visually different from the ones that have children. Right now I have this menu, but that could change:
我正在开发一个带有嵌套子菜单的 wordpress 主题。我需要使没有孩子的元素在视觉上与有孩子的元素不同。现在我有这个菜单,但这可能会改变:
A
a1
a2
B
b1
b2
C
As you can see, A and B have children. C doesn't - I need it to be different in the CSS level.
如您所见,A 和 B 有孩子。C 没有 - 我需要它在 CSS 级别上有所不同。
Ideally, I would like to have a has-children
class in A and B, but not in C.
理想情况下,我希望has-children
在 A 和 B 中上课,而不是在 C 中。
So far I've managed to create a "Menu Walker" PHP class that I can instantiate and pass to wp_nav_menu. Its constructor looks like this:
到目前为止,我已经设法创建了一个“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
}
}
}
So, how do I tell whether $item
has children, or is a leaf?
那么,我如何判断是否$item
有孩子,或者是一片叶子呢?
EDIT: this question was answered by someone called "keesiemeijer" in the Wordpress forums. I'm leaving this bounty expired just in case he wants to reclaim it. Otherwise, I'll be marking my own answer as valid.
编辑:这个问题是由 Wordpress 论坛中名为“keesiemeijer”的人回答的。我要让这个赏金过期,以防他想收回它。否则,我会将自己的答案标记为有效。
采纳答案by kikito
I asked in the WordPress forumand keesiemeijer pointed me to this other post, in which they did the following:
我在WordPress 论坛上提问,keesiemeijer 将我指向另一个帖子,他们在其中做了以下事情:
Instead of modifying start_el
, they modified display_element
, adding the following two lines (lines 37-38 here):
他们没有修改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');
I've left the previous two lines as a spacial reference, and also as a comment to other answers in this post. It seems that wordpress is "trying" to set a ′has_children′ property in $args
, but it's either doing it wrong or in a way I don't understand. In any case, that has_children
parameter is never passed down to start_el
(see sample var_dump of an $args here)
我将前两行留作空间参考,并作为对本文中其他答案的评论。似乎 wordpress 正在“尝试”$args
在 . 在任何情况下,该has_children
参数都不会传递给start_el
(请参阅此处$args 的示例 var_dump )
This might be a bug on the Wordpress version I've got (3.2.1) and might have been fixed in the most recent version.
这可能是我拥有的 Wordpress 版本 (3.2.1) 上的一个错误,并且可能已在最新版本中修复。
In any case, the answer I got in the Wordpress forum is the one that helped me fix it, so I consider this settled. I'll wait for the bounty to expire just in case keesiemeijer wants to put his answer here.
无论如何,我在 Wordpress 论坛中得到的答案是帮助我解决问题的答案,所以我认为这已经解决了。我会等待赏金到期,以防万一keesiemeijer 想把他的答案放在这里。
回答by janw
Add this to functions.php
it will add the 'dropdown' class to parents
添加functions.php
它会将“下拉”类添加到父母
New way beter for performance
提高性能的新方法
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 );
Old: intensive on the DB
旧:数据库密集
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;
}
回答by starrynight.1406
Simple you use this way:
简单你用这种方式:
Explain: I create menu with "walker":
解释:我用“walker”创建菜单:
$walker = new Nav_Walker;
wp_nav_menu(array(
'container'=>'nav',
'container_class'=>'menu',
'menu_class'=>'list-unstyled list-inline',
'walker'=>$walker
));
Class 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...
}
}
}
We have object 'walker', you can var_dump($args)to see more things. I'm using for my project !
我们有对象 'walker',你可以通过var_dump($args)来查看更多的东西。我正在为我的项目使用!
回答by Jorge Bucaran
It seems that the problem has finally been addressed. The latest Wordpress beta as of current writing 4.0, has updated the Walker_Nav_Menu class and added a $has_children
property.
看来问题终于解决了。截至当前编写 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;
So, we don't need to hack function display_element(...)
anymore.
所以,我们不需要再破解function display_element(...)
了。
回答by Eugene Manuilov
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
}
}
}
回答by Chris Krycho
Kikito's answer abovegets the trick done, but not in the most reusable way. In my view, the better approach is like this:
上面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...
}
Overriding Walker::display_element()
is the right move, but it's better to actually address the problem at the root of the issue rather than simply tacking a class on at this point, for two reasons. First, the real problem isn't a missing class but rather the un-patched bug in WordPress that Kikito noted: the problem is that $args[0]
isn't always an array. That appears to be the typically expected type for Walker::display_element()
, but we're actually dealing here with Walker_Nav_Menu::display_element()
, and in that case args
ends up being passed in as a standard object type rather than an array type. As such, we simply need to add the has_children
element using object notation instead of array notation. Problem solved![1]
覆盖Walker::display_element()
是正确的举措,但最好从问题的根源实际解决问题,而不是在这一点上简单地添加一个类,原因有两个。首先,真正的问题不是缺少类,而是 Kikito 指出的 WordPress 中未修补的错误:问题在于它$args[0]
并不总是一个数组。这似乎是 的典型预期类型Walker::display_element()
,但我们实际上在这里处理的是Walker_Nav_Menu::display_element()
,在这种情况下,args
最终作为标准对象类型而不是数组类型传入。因此,我们只需要has_children
使用对象表示法而不是数组表示法来添加元素。问题解决了![1]
Adding that elseif
accounts for the ordinary Nav Menu case. This is the same form that will hopefully make it into the core class in this patch, at which point you'll no longer have to extend it. They should probably patch it further to account for the case that $args[0]
is neither an array nor an object, but I don't expect to see that happen.
添加说明elseif
普通导航菜单案例。这与有望成为本补丁中核心类的形式相同,此时您将不再需要扩展它。他们可能应该进一步修补它以解决$args[0]
既不是数组也不是对象的情况,但我不希望看到这种情况发生。
Second, in order to keep good separation of concerns between the various methods, classes should really be added in the start_el()
method or elsewhere, since display_element()
isn't doing any of the class handling.
其次,为了在各种方法之间保持良好的关注点分离,类应该真正添加到start_el()
方法中或其他地方,因为display_element()
不进行任何类处理。
As a result, you can then override start_el()
however you like: you can add your own custom classes, or ignore elements entirely, or supply custom text, or whatever you like. (In my case, I'm working around an existing Javascript menu implementation that has very specific classing requirements based on parents andchildren, so I can't just add the same classes to everything that has a child –?which is precisely why this separation of concerns matters.) In my code:
因此,您可以随意覆盖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] This is of course one of the potential pitfalls of dynamically typed languages like PHP... it's not a problem, as long as you're careful. The WordPress developers weren't careful here.
[1] 这当然是像 PHP 这样的动态类型语言的潜在缺陷之一……这不是问题,只要你小心。WordPress 开发人员在这里并不小心。
回答by Zachary Schuessler
If you don't want the overhead of a hard query or function, you can do this in jQuery:
如果您不想要硬查询或函数的开销,您可以在 jQuery 中执行此操作:
(function() {
// Add 'has_children' class to menus
jQuery.each(jQuery('.menu-item').has('ul.sub-menu'), function() {
jQuery(this).addClass('has_children');
});
})();
回答by Joakim Ling
Add this to your functions.php
将此添加到您的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;
}
Then in your walker your can check if $item->has_children is true or false
然后在你的步行者中你可以检查 $item->has_children 是真还是假
回答by liying
/**
* @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 );
}
}
}
回答by Kalimah
Since this question is one of the first results on Google search, has been referenced in other webpages and most the of the answers here are outdated I thought I would post this answer.
由于这个问题是谷歌搜索的第一个结果之一,已在其他网页中引用,而且这里的大多数答案已经过时,我想我会发布这个答案。
start_el()has classesas part of $itemobject. So You can add this in start_el():
start_el()将类作为$item对象的一部分。所以你可以在 start_el() 中添加这个:
if(in_array('menu-item-has-children', $item->classes) && $depth != 0)
{
// Your Code
}
Note that $depthis not required in the conditions but if removed your code will be applied to the first item (i.e. item with 0 depth).
请注意,条件中不需要$depth,但如果删除,您的代码将应用于第一个项目(即深度为 0 的项目)。
As for the compatibility part, the class 'menu-item-has-children'has been added to WordPress 3.7 (October 2013)and I have tested it on the latest WordPress 4.4 (as of the time of posting this answer).
至于兼容性部分,“menu-item-has-children”类已添加到WordPress 3.7(2013 年 10 月)中,我已在最新的 WordPress 4.4 上对其进行了测试(截至发布此答案时)。