Wordpress - как узнать, есть ли в элементе меню дети?
Я разрабатываю тему wordpress с вложенными подменю. Мне нужно сделать элементы без детей, визуально отличающихся от тех, у которых есть дети. Прямо сейчас у меня есть это меню, но это может измениться:
A
a1
a2
B
b1
b2
C
Как вы можете видеть, у A и B есть дети. C не - мне нужно, чтобы он отличался на уровне CSS.
В идеале я хотел бы иметь класс has-children
в и B, но не в C.
До сих пор мне удалось создать PHP-класс "Walker", который я могу создать и передать в 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
дочерние элементы или это лист?
EDIT: на этот вопрос ответил кто-то, называемый "keesiemeijer" на форумах Wordpress. Я оставляю эту награду истек, на случай, если он захочет ее вернуть. В противном случае, я буду отмечать свой собственный ответ как действительный.
Ответы
Ответ 1
Я спросил на форуме 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 "пытается" установить свойство "has_children" в $args
, но это либо делает это неправильно, либо каким-то образом я не понимаю. В любом случае этот параметр has_children
никогда не передается до start_el
(см. Пример var_dump $args здесь)
Это может быть ошибка в версии Wordpress, которую я получил (3.2.1), и может быть исправлена в самой последней версии.
В любом случае, ответ, который я получил на форуме Wordpress, - это тот, который помог мне исправить это, поэтому я считаю, что это улажено. Я буду ждать, пока щедрость истечет на случай, если keesiemeijer захочет поставить свой ответ здесь.
Ответ 2
Добавьте это в functions.php
, чтобы добавить родительский класс 'dropdown'
Новый способ измерения производительности
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;
}
Ответ 3
Просто вы используете этот способ:
Объясните:
Я создаю меню с помощью "walker":
$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
Кажется, что проблема наконец решена. Последняя бета-версия Wordpress от текущей записи 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(...)
.
Для реализации подкласса Walker_Nav_Menu, который я написал с использованием встроенной архитектуры фильтра Wordpress, посмотрите здесь.
Ответ 5
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
}
}
}
Ответ 6
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, которую заметил Кикито: проблема в том, что $args[0]
не всегда является массивом. Кажется, это типично ожидаемый тип для Walker::display_element()
, но мы на самом деле имеем дело с Walker_Nav_Menu::display_element()
, и в этом случае args
заканчивается тем, что передается как стандартный тип объекта, а не тип массива. Таким образом, нам просто нужно добавить элемент has_children
, используя нотацию объектов вместо обозначения массива. Проблема решена! [1]
Добавление того, что elseif
учитывает обычный случай Nav Menu. Это та же самая форма, которая, мы надеемся, превратит ее в основной класс в этот патч, после чего вам больше не придется его расширять, Вероятно, они должны исправлять его, чтобы учесть случай, когда $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 не были здесь осторожны.
Ответ 7
Если вы не хотите накладных расходов на трудный запрос или функцию, вы можете сделать это в jQuery:
(function() {
// Add 'has_children' class to menus
jQuery.each(jQuery('.menu-item').has('ul.sub-menu'), function() {
jQuery(this).addClass('has_children');
});
})();
Ответ 8
/**
* @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
Поскольку этот вопрос является одним из первых результатов поиска Google, он упоминается на других веб-страницах, и большинство ответов здесь устарели. Я думал, что опубликую этот ответ.
start_el() имеет классы как часть объекта $item. Поэтому вы можете добавить это в start_el():
if(in_array('menu-item-has-children', $item->classes) && $depth != 0)
{
// Your Code
}
Обратите внимание, что $depth не требуется в условиях, но если вы удалите, ваш код будет применен к первому элементу (т.е. элемент с глубиной 0).
Что касается части совместимости, то класс 'menu-item-have-children' был добавлен в WordPress 3.7 (октябрь 2013), и я тестировал его на последнем WordPress 4.4 (на момент публикации этого ответа).
Ответ 10
Добавьте это в свои функции. 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 истинным или ложным
Ответ 11
Спасибо за функцию Start_el, моя функция выполняет эту функцию для запуска запроса.
У меня функция будет подсчитывать подменю родительского меню по идентификатору.
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;
}
Run
В foreach функции wp_get_nav_menu_items выберите ID родительского меню по $item- > menu_item_parent == 0.
Это работает для меня и очень просто.
Ответ 12
используйте этот простой код в вашем классе walker
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 );
}
}
Ответ 13
Существует простое решение источник.
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';
}
[...]