I'm using WordPress 4.2.1 and I created a custom walker for wp_nav_menu()
In the method start_el() I get the element classes with the following:
function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
...
$classes = empty( $item->classes ) ? array() : ( array ) $item->classes;
...
}
Is there a way to get custom class(es) "only"? (Admin/Appearance/Menus -> CSS Classes)
Someone can help?
Thank you
You could write a function to filter out the wordpress classes...
function item_class(Array $item_class)
{
$cls = array_filter($item_class, function($value) {
return (str_replace(['menu-', 'page_', 'page-'], '', $value) != $value) ? false : true;
});
return implode(' ', $cls);
}
Add to yr walker class, then call with a menu item. eg:
echo '<a class="' . $this->item_class($item->classes) . '">';
No there is no method to get all custom classes in wordpress. But you can use 2 version of wordpress and origional theme and modifty theme and then compare it using software such as codecompare ,Diff Checker or Win Merge.
you can add your classes into walker class like this .
class Description_Walker extends Walker_Nav_Menu
{
/**
* Start the element output.
*
* #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. May be used for padding.
* #param array $args Additional strings.
* #return void
*/
function start_lvl( &$output, $depth = 0, $args = array() ) {
// depth dependent classes
$indent = ( $depth > 0 ? str_repeat( "\t", $depth ) : '' ); // code indent
$display_depth = ( $depth + 1); // because it counts the first submenu as 0
$classes = array(
'nav-submenu',
( $display_depth % 2 ? 'menu-odd' : 'menu-even' ),
( $display_depth >=2 ? 'sub-sub-menu' : '' ),
'menu-depth-' . $display_depth
);
$class_names = implode( ' ', $classes );
// build html
$output .= "\n" . $indent . '<ul class="' . $class_names . '">' . "\n";
}
function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
global $wp_query;
$indent = ( $depth > 0 ? str_repeat( "\t", $depth ) : '' ); // code indent
// depth dependent classes
$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 ) ) );
// build html
$output .= $indent . '<li id="nav-menu-item-'. $item->ID . '" class="' . $depth_class_names . ' ' . $class_names . '">';
// link attributes
$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 ) .'"' : '';
$attributes .= ' class="menu-link ' . ( $depth > 0 ? 'sub-menu-link' : 'main-menu-link' ) . '"';
$item_output = sprintf( '%1$s<a%2$s>%3$s%4$s%5$s</a>%6$s',
$args->before,
$attributes,
$args->link_before,
apply_filters( 'the_title', $item->title, $item->ID ),
$args->link_after,
$args->after
);
// build html
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
}
Related
i have a custom walker menu in my theme and i would like to force the href of the first level menu to always be <a href="javascript:void(0);"> when and only when it has submenus. It would only be applied to the first level, not the other levels. In the admin, i would eventually add an empty custom link. Is there a way to achieve this with my custom walker.
Thanks ;-)
class ffe18_Walker_Nav_Menu extends Walker_Nav_Menu {
/**
* Starts the list before the elements are added.
*
* Adds classes to the unordered list sub-menus.
*
* #param string $output Passed by reference. Used to append additional content.
* #param int $depth Depth of menu item. Used for padding.
* #param array $args An array of arguments. #see wp_nav_menu()
*/
function start_lvl( &$output, $depth = 0, $args = array() ) {
// Depth-dependent classes.
$indent = ( $depth > 0 ? str_repeat( "\t", $depth ) : '' ); // code indent
$display_depth = ( $depth + 1); // because it counts the first submenu as 0
$classes = array(
'sub-menu',
( $display_depth % 2 ? 'menu-odd' : 'menu-even' ),
( $display_depth >=2 ? 'sub-sub-menu' : '' ),
'menu-depth-' . $display_depth
);
$class_names = implode( ' ', $classes );
// Build HTML for output.
$output .= "\n" . $indent . '<ul class="' . $class_names . '">' . "\n";
}
/**
* Start the element output.
*
* Adds main/sub-classes to the list items and links.
*
* #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 array $args An array of arguments. #see wp_nav_menu()
* #param int $id Current item ID.
*/
function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
global $wp_query;
$indent = ( $depth > 0 ? str_repeat( "\t", $depth ) : '' ); // code indent
// Depth-dependent classes.
$depth_classes = array(
( $depth == 0 ? 'main-menu-item' : 'sub-menu-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 ) ) );
// Build HTML.
$output .= $indent . '<li id="nav-menu-item-'. $item->ID . '" class="' . $depth_class_names . ' ' . $class_names . '">';
// Link attributes.
$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 ) .'"' : '';
$attributes .= ' class="menu-link ' . ( $depth > 0 ? 'sub-menu-link' : 'main-menu-link' ) . '"';
// Build HTML output and pass through the proper filter.
$item_output = sprintf( '%1$s<a%2$s>%3$s%4$s%5$s</a>%6$s',
$args->before,
$attributes,
$args->link_before,
apply_filters( 'the_title', $item->title, $item->ID ),
$args->link_after,
$args->after
);
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
}
You can simply achieve this using CSS.
YOUR CSS SELECTOR > a {
pointer-events: none;
}
I have a question that I can't seem to find the right approach to.
I'm currently developing a WordPress site whereby a custom submenu is required. In said submenu, a list of category terms is displayed (incidentally, a list of years – 2003-2017). For each of these years, I have created an image field using the brilliant Advanced Custom Fields PRO plugin. The idea is that an image can be uploaded for a year in the “Edit Category” page. So far, so good. This image would then be shown in the submenu alongside the year, and here's where I am stumped.
What I am having trouble with is figuring out how I am going to check for that field in the submenu and fetch it.
I'll include the code for the custom nav walker below. Any help would be greatly appreciated!
class Nav_Header_Walker extends Walker_Nav_Menu {
public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) {
if ( ! $element ) {
return;
}
$id_field = $this->db_fields['id'];
$id = $element->$id_field;
// Display this element.
$this->has_children = ! empty( $children_elements[ $id ] );
if ( isset( $args[0] ) && is_array( $args[0] ) ) {
$args[0]['has_children'] = $this->has_children; // Backwards compatibility.
}
$cb_args = array_merge( array( &$output, $element, $depth ), $args );
call_user_func_array( array( $this, 'start_el' ), $cb_args );
// Descend only when the depth is right and there are children for this element.
if ( ( 0 === $max_depth || $max_depth > $depth + 1 ) && isset( $children_elements[ $id ] ) ) {
foreach ( $children_elements[ $id ] as $child ) {
if ( ! isset( $newlevel ) ) {
$newlevel = true;
// Start the child delimiter.
$cb_args = array_merge( array( &$output, $depth ), $args );
/** Additional check for custom addition of id to sub-level */
if ( $element->post_name = 'Megatron' ) {
$cb_args['sub_menu_id'] = 'megatron';
}
/** End custom check */
call_user_func_array( array( $this, 'start_lvl' ), $cb_args );
}
$this->display_element( $child, $children_elements, $max_depth, $depth + 1, $args, $output );
}
unset( $children_elements[ $id ] );
}
if ( isset( $newlevel ) && $newlevel ) {
// End the child delimiter.
$cb_args = array_merge( array( &$output, $depth ), $args );
call_user_func_array( array( $this, 'end_lvl' ), $cb_args );
}
// End this element.
$cb_args = array_merge( array( &$output, $element, $depth ), $args );
call_user_func_array( array( $this, 'end_el' ), $cb_args );
}
public function start_lvl( &$output, $depth = 0, $args = array(), $sub_menu_div = null ) {
$indent = str_repeat( "\t", $depth );
if ( $sub_menu_div ) {
$output .= "\n$indent<div id=\"$sub_menu_div\"><ul class=\"sub-menu\">\n";
} else {
$output .= "\n$indent<ul class=\"sub-menu\">\n";
}
}
function end_lvl( &$output, $depth = 0, $args = array() ) {
$indent = str_repeat( "\t", $depth );
$output .= "$indent</ul></div>\n";
}
}
In case anybody ever comes across this rather niche issue in the future, I figured out what I approach I needed.
Adding the following block of code to the end of what I included in my original question did the trick.
Pay close attention to how I retrieve the ACF image field (standard way to get a taxonomy field, if you're not familiar with it), using $item->object and $item->object_id.
It’s then just a case of altering the output to include the background image for each menu item, using the conditional to check whether said item is a category link and is the correct depth (i.e. if it is a submenu item). If it isn't both of those, it doesn't bother with the background image.
function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
if ( in_array( 'menu-item-object-category', $item->classes ) && $depth > 0 ) {
// Get the ACF PRO image field (I have replaced my field with 'field_name', change this to your field)
$thumb = get_field( 'field_name', $item->object . '_' . $item->object_id );
$before = '<li class="' . implode( ' ', $item->classes ) . '">';
$after = '</li>';
// Attributes
$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 ) .'"' : '';
$attributes .= ' style="background-image: url(' . $thumb['url'] . ')"';
$item_output = sprintf( '%1$s<a%2$s>%3$s%4$s%5$s</a>%6$s',
$before,
$attributes,
$args->link_before,
apply_filters( 'the_title', $item->title, $item->ID ),
$args->link_after,
$after
);
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
} else {
$before = '<li class="' . implode( ' ', $item->classes ) . '">';
// link attributes
$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 ) .'"' : '';
$item_output = sprintf( '%1$s<a%2$s>%3$s%4$s%5$s</a>%6$s',
$before,
$attributes,
$args->link_before,
apply_filters( 'the_title', $item->title, $item->ID ),
$args->link_after,
$args->after
);
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
}
function end_el( &$output, $item, $depth = 0, $args = array() ) {
$output .= "</li>\n";
}
How is it possible for me to create a field that adds a custom class to the wordpress navigation items anchor tags and not list items
So I want this:
<li><a class="custom classes" href="example"></a></li>
<li><a class="different custom classes" href="example"></a></li>
<li><a class="other custom classes" href="example"></a></li>
Can someone please help me?
---- Edit :) Sorry lol
Your walker works perfectly on a fresh wp menu. But I tried to add that to the actual theme later on and there is a slight problem :P
class Maha_Mega_Menu extends Walker_Nav_Menu {
function start_lvl(&$output, $depth = 0, $args = array()) {
$indent = str_repeat("\t", $depth);
$output .= "\n$indent<div class=\"nav-sub-menus\"><ul>\n";
}
function end_lvl(&$output, $depth = 0, $args = array()) {
$indent = str_repeat("\t", $depth);
$output .= "$indent</ul></div>\n";
}
function start_el( &$output, $item, $depth = 0, $args = array(), $current_object_id = 0 ) {
global $wp_query;
$cat = $item->object_id;
$indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';
$class_names = $value = '';
$classes = empty( $item->classes ) ? array() : (array) $item->classes;
$class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item ) );
$class_names = ' class="' . esc_attr( $class_names ) . '"';
$output .= $indent . '<li id="menu-item-'. $item->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 ) .'"' : '';
$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>';
$children = get_posts(array(
'post_type' => 'nav_menu_item',
'nopaging' => true,
'numberposts' => 1,
'meta_key' => '_menu_item_menu_item_parent',
'meta_value' => $item->ID
));
// echo $depth.' x ';
if ( ! empty( $children ) || ! get_field( 'menu_latest_posts', 'category_' . $cat ) || get_field( 'menu_latest_posts', 'category_' . $cat ) == 'latest_posts_on' ) {
// if ( $depth == 0 && $item->object == 'category' || $item->object == 'page' ) {
if ( $depth == 0 && $item->object == 'category' || $item->object == 'page' || $item->object == 'custom' ) {
$item_output .= '<div class="nav-sub-wrap container"><div class="nsw row">';
}
}
$item_output .= $args->after;</code>
The list items on this menu are using the $class_names variable for the custom styling the navigation has. So if IO remove the variable from the li and add it to the the anchor tag, I lose the activate states and everything. Is it somehow possible to avoid that?
I mean the only thing I want is to be able to add different classes like: icon event, icon home etc.. to the 6 different anchor tags on the menu.
If you really want a bare-bones navigation with your own classes in the anchor and you're not bothered about additional classes or sub-navigation, then here's an easier method:
<nav>
<ul>
<?php
$nav = wp_get_nav_menu_items( 'main-navigation' );
foreach ( $nav as $nav_item ) {
echo '<li>' . $nav_item->title . '</li>';
}
?>
</ul>
</nav>
In your admin area go to Appearance > Menus.
On the top right of the screen click on 'Screen Options' on the bottom row - make sure 'CSS Classes' is checked.
In your theme file (where your wp_nav) is you have to add a custom walker.
<?php wp_nav_menu( array('walker' => new Class_Name_Walker ) );?>
In your functions.php - This adds all the classes that are added to the li to all the a as well
class Class_Name_Walker extends Walker_Nav_Menu
{
/**
* Start the element output.
*
* #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. May be used for padding.
* #param array $args Additional strings.
* #return void
*/
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;
$class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args ) );
$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
$id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args );
$id = $id ? ' id="' . esc_attr( $id ) . '"' : '';
$output .= $indent . '<li' . $id . $value .'>';
$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 );
$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 .$class_names.'>';
$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 );
}
/**
* #see Walker::end_el()
* #since 3.0.0
*
* #param string $output Passed by reference. Used to append additional content.
* #param object $item Page data object. Not used.
* #param int $depth Depth of page. Not Used.
*/
function end_el( &$output, $item, $depth = 0, $args = array() ) {
$output .= "</li>\n";
}
}
I have a menu with 10 parent pages in Wordpress. Each parent page have alot of children (sub-pages). I would like to append to each menu item a number showing how many children that item has.
EXAMPLE:
Standard menu: Products | Portfolio | Store | Jobs
Menu i want: Products (49) | Portfolio (14) | Store (22) | Jobs (51)
This indicates that the parent page "Products" has 49 children. "Portfolio" has 14 etc. Adding new sub-pages to these parent pages would automatically update the number.
I am using the default menu that wordpress creates of your pages, and not the menu-system found under apperance. Using TwentyThirteen theme.
Anyone?
Anders
In such case you can use the walker. Put the following class in your functions.php file
class themeslug_walker_nav_menu extends Walker_Nav_Menu {
// add main/sub classes to li's and links
function start_el( &$output, $item, $depth, $args ) {
global $wp_query;
$indent = ( $depth > 0 ? str_repeat( "\t", $depth ) : '' ); // code indent
// depth dependent classes
$depth_classes = array(
( $depth == 0 ? 'main-menu-item' : 'sub-menu-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 ) ) );
// build html
$output .= $indent . '<li id="nav-menu-item-'. $item->ID . '" class="' . $depth_class_names . ' ' . $class_names . '">';
// link attributes
$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 ) .'"' : '';
$attributes .= ' class="menu-link ' . ( $depth > 0 ? 'sub-menu-link' : 'main-menu-link' ) . '"';
$childs = count(get_pages('child_of='.$item->object_id.'&parent='.$item->object_id));
$item_output = sprintf( '%1$s<a%2$s>%3$s%4$s (%5$s) %6$s</a>%7$s',
$args->before,
$attributes,
$args->link_before,
apply_filters( 'the_title', $item->title, $item->ID ),
$childs,
$args->link_after,
$args->after
);
// build html
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
}
and add this walker in your wp_nav_menu function
<?php wp_nav_menu( array( 'theme_location' => 'primary', 'menu_class' => 'nav-menu', 'walker' => new themeslug_walker_nav_menu ) ); ?>
That's it.
I'm currently using a custom walker for my WP menu in WordPress but I just need 1 final amendment. I need to know how to...
Give an 'odd' class to all odd li's within all 'sub-menu' ul's.
So that's the first 'sub-menu' after a parent, and all 'sub-menu' ul's within it.
Here's the custom walker I'm currently using so if you could edit this that would be awesome!
class Menu_With_Description extends Walker_Nav_Menu
{
function start_lvl(&$output, $depth) {
$indent = str_repeat("\t", $depth);
$output .= "\n$indent<ul class=\"sub-menu level-".$depth."\">\n";
}
function end_lvl(&$output, $depth) {
$indent = str_repeat("\t", $depth);
$output .= "$indent<div class=\"fix\"></div></ul><div class=\"fix\"></div>\n";
}
/**
* Start the element output.
*
* #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. May be used for padding.
* #param array $args Additional strings.
* #return void
*/
function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output )
{
$id_field = $this->db_fields['id'];
if ( is_object( $args[0] ) ) {
$args[0]->has_children = ! empty( $children_elements[$element->$id_field] );
}
return parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
}
function start_el(&$output, $item, $depth, $args)
{
$classes = empty ( $item->classes ) ? array () : (array) $item->classes;
$class_names = join(
' '
, apply_filters(
'nav_menu_css_class'
, array_filter( $classes ), $item
)
);
if ($args->has_children && $depth == 0){
! empty ( $class_names )
and $class_names = ' class="'. esc_attr( $class_names ) . ' has_children"';
}else{
! empty ( $class_names )
and $class_names = ' class="'. esc_attr( $class_names ) . '"';
}
$output .= "<li id='menu-item-$item->ID' $class_names>" ;
$attributes = '';
! empty( $item->attr_title )
and $attributes .= ' title="' . esc_attr( $item->attr_title ) .'"';
! empty( $item->target )
and $attributes .= ' target="' . esc_attr( $item->target ) .'"';
! empty( $item->xfn )
and $attributes .= ' rel="' . esc_attr( $item->xfn ) .'"';
! empty( $item->url )
and $attributes .= ' href="' . esc_attr( $item->url ) .'"';
// insert description for top level elements only
// you may change this
$description = ( ! empty ( $item->description ) and 0 == $depth )
? '<span class="description">' . esc_attr( $item->description ) . '</span>' : '';
$title = apply_filters( 'the_title', $item->title, $item->ID );
if ( $depth == 0 ) {//top level items
$item_output = $args->before
."<div class='parent'><div class='cat-icon'></div><div class='title-desc'>"
. "<a $attributes>"
. $args->link_before
. $title
. '</a><br>'
. $args->link_after
. $description
. '</div></div>'
. $args->after;
}else{//everything else
$item_output = $args->before
. "<a $attributes>"
. $args->link_before
. $title
. '</a> '
. $args->link_after
. $args->after;
}
// Since $output is called by reference we don't need to return anything.
$output .= apply_filters(
'walker_nav_menu_start_el'
, $item_output
, $item
, $depth
, $args
);
}
}
That's my fast solution to your problem (but maybe someone find simpler one):
Define protected/private variable for your walker class. This is an array with current items counters on every depth level. Because start_lvl method isn't called before first item of 0 level, you have to fill 0 index of your array by 0 value.
protected $_itemsNoByDepth = array( 0 => 0 );
When you start next level you have to set level counter to 0 in start_lvl method. Because first sub-level have $depth value 0 you have add 1 to this value.
$this->_itemsNoByDepth[$depth+1] = 0;
Finally you have to iterate proper counter in every occurrence of start_el method. After it you can use this counter to add required classes as a new $classes array indexes. In your case it will be:
$this->_itemsNoByDepth[$depth]++;
if( $depth > 0 && $this->_itemsNoByDepth[$depth] % 2 ) {
$classes[] = 'odd';
}
but if you'd like to add eg. odd/even classes and classes with consecutive items numbers, you can do sth like:
$this->_itemsNoByDepth[$depth]++;
$classes[] = 'item-no-' . $this->_itemsNoByDepth[$depth];
$classes[] = 'item-' . ( $this->_itemsNoByDepth[$depth] % 2 ? 'odd' : 'even' );
Remember to put above code between $classes and $class_names variable declarations.
After these three steps your code should looks like that:
class Menu_With_Description extends Walker_Nav_Menu
{
protected $_itemsNoByDepth = array( 0 => 0 );
function start_lvl(&$output, $depth) {
$this->_itemsNoByDepth[$depth+1] = 0;
$indent = str_repeat("\t", $depth);
$output .= "\n$indent<ul class=\"sub-menu level-".$depth."\">\n";
}
function end_lvl(&$output, $depth) {
$indent = str_repeat("\t", $depth);
$output .= "$indent<div class=\"fix\"></div></ul><div class=\"fix\"></div>\n";
}
/**
* Start the element output.
*
* #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. May be used for padding.
* #param array $args Additional strings.
* #return void
*/
function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output )
{
$id_field = $this->db_fields['id'];
if ( is_object( $args[0] ) ) {
$args[0]->has_children = ! empty( $children_elements[$element->$id_field] );
}
return parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
}
function start_el(&$output, $item, $depth, $args)
{
$classes = empty ( $item->classes ) ? array () : (array) $item->classes;
$this->_itemsNoByDepth[$depth]++;
if( $depth > 0 && $this->_itemsNoByDepth[$depth] % 2 ) {
$classes[] = 'odd';
}
$class_names = join(
' '
, apply_filters(
'nav_menu_css_class'
, array_filter( $classes ), $item
)
);
if ($args->has_children && $depth == 0){
! empty ( $class_names )
and $class_names = ' class="'. esc_attr( $class_names ) . ' has_children"';
}else{
! empty ( $class_names )
and $class_names = ' class="'. esc_attr( $class_names ) . '"';
}
$output .= "<li id='menu-item-$item->ID' $class_names>" ;
$attributes = '';
! empty( $item->attr_title )
and $attributes .= ' title="' . esc_attr( $item->attr_title ) .'"';
! empty( $item->target )
and $attributes .= ' target="' . esc_attr( $item->target ) .'"';
! empty( $item->xfn )
and $attributes .= ' rel="' . esc_attr( $item->xfn ) .'"';
! empty( $item->url )
and $attributes .= ' href="' . esc_attr( $item->url ) .'"';
// insert description for top level elements only
// you may change this
$description = ( ! empty ( $item->description ) and 0 == $depth )
? '<span class="description">' . esc_attr( $item->description ) . '</span>' : '';
$title = apply_filters( 'the_title', $item->title, $item->ID );
if ( $depth == 0 ) {//top level items
$item_output = $args->before
."<div class='parent'><div class='cat-icon'></div><div class='title-desc'>"
. "<a $attributes>"
. $args->link_before
. $title
. '</a><br>'
. $args->link_after
. $description
. '</div></div>'
. $args->after;
}else{//everything else
$item_output = $args->before
. "<a $attributes>"
. $args->link_before
. $title
. '</a> '
. $args->link_after
. $args->after;
}
// Since $output is called by reference we don't need to return anything.
$output .= apply_filters(
'walker_nav_menu_start_el'
, $item_output
, $item
, $depth
, $args
);
}
}