Ask your WordPress questions! Pay money and get answers fast! (more info)

Custom Walker Class wp_nav_menu()

  • SOLVED

EDIT: I bumped up the prize to $32. Really hoping for a solution today. Thanks a lot guys I appreciate your hard work.

Hi,

I found a custom Nav Walker that is used to modify the wp_nav_menu() function and display a sub-navigation.

Everything works as expected but the only issue is that it adds an additional </li> tag at the end of the output. Its not a huge deal, but it's preventing my page from validating, so I'd like to have it removed.

The sample output looks like the following:

<ul class="sub-menu">
<li><a href="#"><span>Sample item 1</span></a></li>
<li><a href="#"><span>Sample item 2</span></a>

<ul class="sub-menu">
<li><a href="#"><span>Sample Sub Item 1</span></a></li>
</ul>
</li>
<li><a href="#"><span>Sample item 3</span></a></li>
<li><a href="#"><span>Sample item 4</span></a>
</ul>
</li>



The custom nav walker is below. I mainly just want to remove the trailing </li>, but if you see other changes that need to be made please feel free. (I'm fairly new to custom nav walkers so don't know a ton about the proper syntax, etc).

Thanks a lot for your help.

Custom nav walker:

class sub_nav_walker extends Walker_Nav_Menu {

var $found_parents = array();

function start_el(&$output, $item, $depth, $args) {
global $wp_query;

//this only works for second level sub navigations
$parent_item_id = 0;

$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 ) . '"';
#current_page_item

// Checks if the current element is in the current selection
if (strpos($class_names, 'current-menu-item')
|| strpos($class_names, 'current-menu-parent')
|| strpos($class_names, 'current-menu-ancestor')
|| (is_array($this->found_parents) && in_array( $item->menu_item_parent, $this->found_parents )) ) {

// Keep track of all selected parents
$this->found_parents[] = $item->ID;

//check if the item_parent matches the current item_parent
if($item->menu_item_parent!=$parent_item_id){

$output .= $indent . '<li' . $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 .'><span>';
$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
$item_output .= '</span></a>';
$item_output .= $args->after;
}
//}
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
}

function end_el(&$output, $item, $depth) {
// Closes only the opened li
if ( is_array($this->found_parents) && in_array( $item->ID, $this->found_parents ) ) {
$output .= "</li>\n";
}
}

function end_lvl(&$output, $depth) {
$indent = str_repeat("\t", $depth);
// If the sub-menu is empty, strip the opening tag, else closes it
if (substr($output, -22)=="<ul class=\"sub-menu\">\n") {
$output = substr($output, 0, strlen($output)-23);
} else {
$output .= "$indent</ul>\n";
}
}
}

Answers (6)

2011-01-26

rilwis answers:

Hi,

Please try this code: http://pastebin.com/Hxnf3WWb

Replace it with the old "sub_nav_walker" class.


WP Answers comments:

This works like a charm!

Thank you so much brother.

2011-01-26

Chris Lee answers:

Can we see the markup where you call the wp_nav_menu() function?


WP Answers comments:

Sure thing. Here you go:


<?php
if (function_exists('wp_nav_menu')) {
$menu_args = array('walker' => new sub_nav_walker(),);
echo '<div id="sub_nav">';
wp_nav_menu($menu_args);
echo '</div><!-- end sub_nav -->';
};?>

2011-01-26

Dan | gteh answers:

The issue is definitely with this function



// Closes only the opened li

if ( is_array($this->found_parents) && in_array( $item->ID, $this->found_parents ) ) {

$output .= "</li>\n";

}

Try replacing the entire code with this:


class sub_nav_walker extends Walker_Nav_Menu {



var $found_parents = array();



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

global $wp_query;



//this only works for second level sub navigations

$parent_item_id = 0;



$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 ) . '"';

#current_page_item



// Checks if the current element is in the current selection

if (strpos($class_names, 'current-menu-item')

|| strpos($class_names, 'current-menu-parent')

|| strpos($class_names, 'current-menu-ancestor')

|| (is_array($this->found_parents) && in_array( $item->menu_item_parent, $this->found_parents )) ) {



// Keep track of all selected parents

$this->found_parents[] = $item->ID;



//check if the item_parent matches the current item_parent

if($item->menu_item_parent!=$parent_item_id){



$output .= $indent . '<li' . $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 .'><span>';

$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;

$item_output .= '</span></a></li>';

$item_output .= $args->after;

}

//}

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

}

}



function end_el(&$output, $item, $depth) {

// Closes only the opened li

if ( is_array($this->found_parents) && in_array( $item->ID, $this->found_parents ) ) {

$output .= "";

}

}



function end_lvl(&$output, $depth) {

$indent = str_repeat("\t", $depth);

// If the sub-menu is empty, strip the opening tag, else closes it

if (substr($output, -22)=="<ul class=\"sub-menu\">\n") {

$output = substr($output, 0, strlen($output)-23);

} else {

$output .= "$indent</ul>\n";

}

}

}





main change to:

$item_output .= '</span></a>';

added the closing </li> to that spot and removed it from the end_lvl function.


back up your code first though.


WP Answers comments:

Thank you for this.

This fixed the trailing 'li' tag, but the 3rd level sub-navs gets a little jacked up. Using the example from my original post, you will see what I mean. It essentially closes out the 'li' before nesting the 3rd level nav:


<ul class="sub-menu">
<li><a href="#"><span>Sample item 1</span></a></li>
<li><a href="#"><span>Sample item 2</span></a></li>
<ul class="sub-menu">
<li><a href="#"><span>Sample Sub Item 1</span></a></li>
</ul>



<li><a href="#"><span>Sample item 3</span></a></li>
<li><a href="#"><span>Sample item 4</span></a>
</ul>


Dan | gteh comments:

Your original code is putting the last </li> after the closing </ul> leaving the last list item without a closing tag completely.

Here's an updated version. Try this one. If this doesn't work I am unsure what else could be causing it.


class sub_nav_walker extends Walker_Nav_Menu {

var $found_parents = array();


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



global $wp_query;







//this only works for second level sub navigations



$parent_item_id = 0;







$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 ) . '"';



#current_page_item







// Checks if the current element is in the current selection



if (strpos($class_names, 'current-menu-item')



|| strpos($class_names, 'current-menu-parent')



|| strpos($class_names, 'current-menu-ancestor')



|| (is_array($this->found_parents) && in_array( $item->menu_item_parent, $this->found_parents )) ) {







// Keep track of all selected parents



$this->found_parents[] = $item->ID;







//check if the item_parent matches the current item_parent



if($item->menu_item_parent!=$parent_item_id){







$output .= $indent . '<li' . $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 .'><span>';



$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;



$item_output .= '</span></a></li>';



$item_output .= $args->after;



}



//}



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



}



}







function end_el(&$output, $item, $depth) {



// Closes only the opened li



if ( is_array($this->found_parents) && in_array( $item->ID, $this->found_parents ) ) {



$output .= "</li>\n";



}



}







function end_lvl(&$output, $depth) {



$indent = str_repeat("\t", $depth);



// If the sub-menu is empty, strip the opening tag, else closes it



if (substr($output, -22)=="<ul class=\"sub-menu\">\n") {



$output = substr($output, 0, strlen($output)-23);



} else {



$output .= "$indent</ul>\n";



}



}



}




WP Answers comments:

Thank you again.

The new code is still incorrect.

Its adding two closing 'li' tags to each item, and also has the 3rd level nesting issue, as well as the original issue with the trailing 'li'

If you, or anyone else, has another walker that you've used in the past, I'm certainly open to using that. I'm just looking for a walker that displays all of the sub-pages of a particular section, in nested lists, without listing the parent item.


Dan | gteh comments:

Here is one I've used in the past. Had to dig it out from a client's site.



class samplesite_walker extends Walker_Nav_Menu {

function start_lvl(&$output, $depth) {
$indent = str_repeat("\t", $depth);
if ($depth == 0)
$output .= "\n$indent<div class=\"drop\"><ul class=\"sub-menu\">\n";
else
$output .= "\n$indent<ul class=\"sub-menu\">\n";
}

function end_lvl(&$output, $depth) {
$indent = str_repeat("\t", $depth);
if ($depth == 0)
$output .= "$indent</ul></div>\n";
else
$output .= "$indent</ul>\n";
}

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;

$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 .' class="'.strtolower(str_replace(' ','-',preg_replace('/[^a-zA-Z0-9\s] /', '', $item->title))).'">';
$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 );
}
}


WP Answers comments:

Thank you for the custom walker, but this one is calling in the entire navigation, rather then just the sub pages of the particular section.


Dan | gteh comments:

Ok. I'm all out :) Good luck though.


WP Answers comments:

Thanks brother. I really do appreciate you trying. Have a great day.

2011-01-26

Sébastien | French WordpressDesigner answers:

the code must be

<?php
class sub_nav_walker extends Walker_Nav_Menu {
var $found_parents = array();
function start_el(&$output, $item, $depth, $args) {
global $wp_query;
//this only works for second level sub navigations
$parent_item_id = 0;
$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 ) . '"';
#current_page_item
// Checks if the current element is in the current selection
if (
strpos($class_names, 'current-menu-item')
|| strpos($class_names, 'current-menu-parent')
|| strpos($class_names, 'current-menu-ancestor')
|| (is_array($this->found_parents) && in_array( $item->menu_item_parent, $this->found_parents )) ) {
// Keep track of all selected parents
$this->found_parents[] = $item->ID;
//check if the item_parent matches the current item_parent
if($item->menu_item_parent!=$parent_item_id){
$output .= $indent . '<li' . $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 .'><span>';
$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
$item_output .= '</span></a><li>';
$item_output .= $args->after;


}
//}
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
}
function end_el(&$output, $item, $depth) {
// Closes only the opened li
if ( is_array($this->found_parents) && in_array( $item->ID, $this->found_parents ) ) {
$output .= "\n";
}
}
function end_lvl(&$output, $depth) {
$indent = str_repeat("\t", $depth);
// If the sub-menu is empty, strip the opening tag, else closes it
if (substr($output, -22)=="<ul class=\"sub-menu\">\n") {
$output = substr($output, 0, strlen($output)-23);
} else {
$output .= "$indent</ul>\n";
}
}
}
?>


edit : no, yhat's not good with the third level


WP Answers comments:

Thank you for this. This is very close but has the same issue as one of the solutions above.

When a nav item has a 3rd level sub-nav, the 'li' is closing before the nested 'ul', rather than wrapping the nested 'ul' and closing after that. See the sample below for an example. ('Sample Item 2 should not insert the closing 'li' until after the nested sub-nav below it)



<ul class="sub-menu">
<li><a href="#"><span>Sample item 1</span></a></li>
<li><a href="#"><span>Sample item 2</span></a></li>

<ul class="sub-menu">
<li><a href="#"><span>Sample Sub Item 1</span></a></li>
</ul>

<li><a href="#"><span>Sample item 3</span></a></li>
<li><a href="#"><span>Sample item 4</span></a>
</ul>


Sébastien | French WordpressDesigner comments:

the code must be simply that :


class sub_nav_walker extends Walker_Nav_Menu {
var $found_parents = array();
function start_el(&$output, $item, $depth, $args) {
global $wp_query;
//this only works for second level sub navigations
$parent_item_id = 0;
$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 ) . '"';
#current_page_item
// Checks if the current element is in the current selection
if (strpos($class_names, 'current-menu-item')
|| strpos($class_names, 'current-menu-parent')
|| strpos($class_names, 'current-menu-ancestor')
|| (is_array($this->found_parents) && in_array( $item->menu_item_parent, $this->found_parents )) ) {
// Keep track of all selected parents
$this->found_parents[] = $item->ID;
//check if the item_parent matches the current item_parent
if($item->menu_item_parent!=$parent_item_id){
$output .= $indent . '<li' . $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 .'><span>';
$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
$item_output .= '</span></a>';
$item_output .= $args->after;
}
//}
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
}

}


WP Answers comments:

Hi,

This fixes the 3rd level issue, but the trailing 'li' gets added again, and it also adds a bunch of empty tags after the trailing 'li'

Sorry I feel like I'm being in pain in the arse. Don't mean to be though :)

End of the output looks like the following:


</li>

<ul class="sub-menu">
</li>
</li>
</li>
</li>
</ul>
</li>
</li>
</li>



WP Answers comments:

Hi Mate,

Your last comment didn't get posted. Can you please re-post? Prize is now $32. Cheers.


Sébastien | French WordpressDesigner comments:

This code works :-)
class sub_nav_walker extends Walker_Nav_Menu {


var $found_parents = array();

function start_el(&$output, $item, $depth, $args) {
global $wp_query;
//this only works for second level sub navigations
$parent_item_id = 0;
$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 ) . '"';
#current_page_item
// Checks if the current element is in the current selection
if (strpos($class_names, 'current-menu-item')
|| strpos($class_names, 'current-menu-parent')
|| strpos($class_names, 'current-menu-ancestor')
|| (is_array($this->found_parents) && in_array( $item->menu_item_parent, $this->found_parents )) ) {//$item->menu_item_parent > id du item parent direct
$this->found_parents[] = $item->ID;
if($item->menu_item_parent!=$parent_item_id){
$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 .'><span>';
$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
$item_output .= '</span></a>';
$item_output .= $args->after;
}
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );

$new = str_replace(array("\r\n", "\n", "\r", "\t"),'', $output);
if(ereg('<ul class="sub-menu"></ul>',$new)){
$output = str_replace('<ul class="sub-menu"></ul>','', $new);
}
}
}

function end_el(&$output, $item, $depth) {
// Closes only the opened li
if (strpos($class_names, 'current-menu-item')
|| strpos($class_names, 'current-menu-parent')
|| strpos($class_names, 'current-menu-ancestor')
|| (is_array($this->found_parents) && in_array( $item->menu_item_parent, $this->found_parents )) ) {
$output .= "</li>\n";
}
}

}

2011-01-26

Gabriel Reguly answers:

Hi,

Why are you using a custom walker?

The default walker surely can do the sub levels.

Sorry if I miss something here.

Kind Regards,
Gabriel Reguly


Gabriel Reguly comments:

Sorry, just read the other answers.

You are looking for only the sub pages of a particular section.

This surely can be done. Let me try.


WP Answers comments:

Hi,

I'm totally open to using the default walker if it can achieve what I'm after.

I'm simply looking to create a sub-navigation that lists only the child-pages of the parent section you are currently in. (unlimited levels deep + without displaying parent page).

Please let me know if this is possible by default.


Gabriel Reguly comments:

I don't think it is possible by default.

First one needs to find all the sub items of the current item ( I don't know how hard that could be )
and then use the following code:


function walk_nav_menu_tree( $items, $depth, $r ) {
$walker = ( empty($r->walker) ) ? new Walker_Nav_Menu : $r->walker;
$args = array( $items, $depth, $r );

return call_user_func_array( array(&$walker, 'walk'), $args );
}


$items is an array containing the found sub items
$depth should be 0
$r is the original $args from wp_nav_menu()

Hope that helps you.

Need to go, maybe tomorrow I can help you properly.

Regards,
Gabriel

2011-01-26

John Cotton answers:

Perhaps I'm being a bit thick here, but if you only want sub-level menu items, why don't you just test for those with the $depth value?

So you've have code like this:


// Checks if the current element is in the current selection

if ($depth > 0) {

$output .= $indent . '<li' . $class_names .'>';
//etc
}


and this:


function end_el(&$output, $item, $depth) {
// Closes only the opened li

if ( $depth > 0 ) {
$output .= "</li>\n";
}
}


WP Answers comments:

Hi John,

Thanks for this solution.

The nav is displaying correctly, however it is displaying "2 empty subnavs". One gets displayed before, and the other gets displayed after. Any ideas around this?

Here's a sample of what's being outputted:


<ul class="sub-menu">
<li></li>
<li></li>
<li></li>
</ul>



<ul class="sub-menu">
<li><a href="#"><span>Sample item 1</span></a></li>
<li><a href="#"><span>Sample item 2</span></a>

<ul class="sub-menu">
<li><a href="#"><span>Sample Sub Item 1</span></a></li>
</ul></li>

<li><a href="#"><span>Sample item 3</span></a></li>
<li><a href="#"><span>Sample item 4</span></a>
</ul>



<ul class="sub-menu">
<li></li>
<li></li>
<li></li>
<li></li>
</ul>


John Cotton comments:

Are you able to show the output of the menu using the standard walker? That might give a clue as to what's wrong.....


John Cotton comments:

As a test you could try changing this line


$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;


to this


$item_output .= $args->link_before . $item->title . $args->link_after;



WP Answers comments:

Hi John,

When I make the $item_output change the output is the same.

I only have one other function that affects the default wp_nav_menu() output. I tried removing it for testing purposes, but it had no affect on the output, other than wrapping everything in one more 'ul'. The function simply strips the default wrapper 'ul' added by the wp_nav_menu() output. I need to run but i'll be back online in approx 2 hours. I've listed the other function below. Thanks a lot for your help so far.

If you or anyone needs more prize money to help me come to a resolution, just let me know and we'll work something out.


// remove wp_nav_menu ul container
function my_nav_unlister($menu){
return preg_replace( array( '#^<ul[^>]*>#', '#</ul>$#' ), '', $menu );
}
add_filter( 'wp_nav_menu', 'my_nav_unlister' );


John Cotton comments:

Without knowing what's in your menu it's difficult to second guess.

if you could show us the output of




<?php

if (function_exists('wp_nav_menu')) {

$menu_args = array();

echo '<div id="sub_nav">';

wp_nav_menu($menu_args);

echo '</div><!-- end sub_nav -->';

};?>




that would help. I shall have packed up the day in 2 hours, but I'll have a look at your default output in the morning.

JC


WP Answers comments:

Sure thing John. The default output is below. I've changed over the links and titles since its a dev. site and I don't want to list the actual links to the public. Please keep in mind the purpose of this custom walker is to display *only* all of the child pages of the particular parent section you're in, without displaying the parent page itself in the list.

Thanks again for your help.

Cheers.


<div id="sub_nav"><li><a href="http://www.website.com/">Features</a>
<ul class="sub-menu">
<li><a href="http://www.website.com/features/">feature 1 sub</a></li>
<li><a href="http://www.website.com/features/">feature 2 sub</a></li>
<li><a href="http://www.website.com/features/">feature 3 sub</a></li>
</ul>
</li>

<li><a href="http://www.website.com/pages/">Pages</a>
<ul class="sub-menu">
<li><a href="http://www.website.com/pages/">page 1 sub</a></li>
<li><a href="http://www.website.com/pages/">page 2 sub</a></li>
<li><a href="http://www.website.com/pages/">page 3 sub</a></li>
<li><a href="http://www.website.com/pages/">page 4 sub</a>

<ul class="sub-menu">
<li><a href="http://www.website.com/pages/page4/">page 4 sub sub</a></li>
</ul>
</li>
<li><a href="http://www.website.com/pages/">page 5 sub</a></li>
<li><a href="http://www.website.com/pages/">page 6 sub</a></li>
<li><a href="http://www.website.com/pages/">page 7 sub</a></li>
<li><a href="http://www.website.com/pages/">page 8 sub</a></li>
<li><a href="http://www.website.com/pages/">page 9 sub</a></li>
<li><a href="http://www.website.com/pages/">page 10 sub</a></li>
</ul>
</li>
<li><a href="http://www.website.com/gallery/">Gallery</a>
<ul class="sub-menu">
<li><a href="http://www.website.com/gallery/">gallery 1 sub</a></li>
<li><a href="http://www.website.com/gallery/">gallery 2 sub</a></li>
<li><a href="http://www.website.com/gallery/">gallery 3 sub</a></li>
<li><a href="http://www.website.com/gallery/">gallery 4 sub</a></li>
</ul>
</li>
<li><a href="http://www.website.com/blog">Blog</a></li>
<li><a href="http://www.website.com/contact">Contact</a></li>
</div><!-- end sub_nav -->


John Cotton comments:

OK, I get it now.

You need to put back in your test for parent ids. Probably the easiest thing is to stick the current code on PasteBin.com and I'll fix it.

JC


WP Answers comments:

Thanks John. So just to clarify, you would like me to paste my original sub_nav walker into pasteBin correct?


John Cotton comments:

No wherever you've got to with it.


WP Answers comments:

Sorry Brother I'm confused about what you need me to post.

Here are 3 pastebin links. Please let me know if this is what you're after:

Walker Class: http://pastebin.com/CdgRNpFn

Sub-Nav Output (using above Walker): http://pastebin.com/qCJay8vV

Regular wp_nav_menu() Output (no walker): http://pastebin.com/02Z9g3Zs

Cheers.


John Cotton comments:

Try this:


class sub_nav_walker extends Walker_Nav_Menu {

function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {

if ( !$element )
return;

global $post;

if ( ($depth == 0) && $element->object_id != $post->ID ) {
return;
}

parent::display_element($element, &$children_elements, $max_depth, $depth=0, $args, $output );
}



function start_el(&$output, $item, $depth, $args) {
global $post;

if ( ($depth == 0) && $item->object_id == $post->ID ) {
return;
}

parent::start_el($output, $item, $depth, $args);
}


function end_el(&$output, $item, $depth) {
if( $depth == 0 )
return;

parent::end_el($output, $item, $depth);
}
}



WP Answers comments:

Hi John,

Nothing at all gets outputted when using the above code.


John Cotton comments:

<blockquote>

Please keep in mind the purpose of this custom walker is to display *only* all of the child pages of the particular parent section you're in, without displaying the parent page itself in the list.
</blockquote>

Isn't that what's supposed to happen - except on the parent page?

Are you looking on a page that has a menu item?

Have a look here and you'll see it working on the right-hand side under test:

[[LINK href="http://wp-test.dynamicarray.co.uk/?page_id=2"]]http://wp-test.dynamicarray.co.uk/?page_id=2[[/LINK]]


WP Answers comments:

Hi John,

The intended function of the walker to to display a simple sub-navigation. So no matter what page you are on in a particular section, it will show all of the pages in that section (excluding the parent-level page from the sub-nav list)

Please look at this as an example:

<ul class="sub-menu">
<li><a href="#"><span>Page 1</span></a></li>

<li><a href="#"><span>Page 2</span></a>
<ul class="sub-menu">
<li><a href="#"><span>Sub A</span></a></li>
<li><a href="#"><span>Sub B</span></a>
<ul class="sub-menu">
<li><a href="#"><span>Sub Sub 1</span></a></li>
<li><a href="#"><span>Sub Sub 2</span></a></li>
<li><a href="#"><span>Sub Sub 3</span></a></li>
</ul></li>
<li><a href="#"><span>Sub C</span></a></li>
</ul></li>

<li><a href="#"><span>Page 3</span></a></li>

<li><a href="#"><span>Page 4</span></a>
</ul>


If I were on Page 2 or any of its child pages, the sub-navigation should look like this:

<ul class="sub-menu">
<li><a href="#"><span>Sub A</span></a></li>
<li><a href="#"><span>Sub B</span></a>
<ul class="sub-menu">
<li><a href="#"><span>Sub Sub 1</span></a></li>
<li><a href="#"><span>Sub Sub 2</span></a></li>
<li><a href="#"><span>Sub Sub 3</span></a></li>
</ul></li>
<li><a href="#"><span>Sub C</span></a></li>
</ul></li>
</ul>


I hope this makes sense.

Cheers.


John Cotton comments:

Are Sub A and Sub Sub 1 etc all pages that are children of Page 2?


WP Answers comments:

Yes. All of them are child pages of "Page 2"

(Sub A, Sub B, Sub Sub 1, Sub Sub 2, Sub Sub 3, Sub C)


John Cotton comments:

OK - I'm sure this is right now:

It displays all descendants of the top level page of the current page (including if the current page is the top level page)



class sub_nav_walker extends Walker_Nav_Menu {

function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {

if ( !$element )
return;

global $post;

if ( $depth == 0) {
if($element->object_id == $post->ID) {
$id_field = $this->db_fields['id'];
$id = $element->$id_field;

// descend only when the depth is right and there are childrens for this element
if ( ($max_depth == 0 || $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);
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 ] );
}

return;

} else {
$_current_page = get_page( $post->ID );

if ( isset($_current_page->ancestors) && !in_array($element->object_id, (array) $_current_page->ancestors) ) {
return;
}
}
}

parent::display_element($element, &$children_elements, $max_depth, $depth=0, $args, $output );
}



function start_el(&$output, $item, $depth, $args) {
global $post;

if ( $depth == 0) {
$_current_page = get_page( $post->ID );

if ( isset($_current_page->ancestors) && in_array($item->object_id, (array) $_current_page->ancestors) )
return;
}

parent::start_el($output, $item, $depth, $args);
}


function end_el(&$output, $item, $depth) {
if ( $depth == 0) {
$_current_page = get_page( $post->ID );

if ( isset($_current_page->ancestors) && in_array($item->object_id, (array) $_current_page->ancestors) )
return;
}


parent::end_el($output, $item, $depth);
}
}





John Cotton comments:

Here's a slightly tidier version of the code:



class sub_nav_walker extends Walker_Nav_Menu {

var $_current_id;
var $_current_page;

function sub_nav_walker() {
global $post;

$this->_current_id = $post->ID;
$this->_current_page = get_page( $this->_current_id );
}

function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {

if ( !$element )
return;

if ( $depth == 0) {
if($element->object_id == $this->_current_id) {
$id_field = $this->db_fields['id'];
$id = $element->$id_field;

// descend only when there are childrens for this element
if ( 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);
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 ] );
}

return;

} elseif ( isset($this->_current_page->ancestors) && !in_array($element->object_id, (array) $this->_current_page->ancestors) ) {
return;
}
}

parent::display_element($element, &$children_elements, $max_depth, $depth=0, $args, $output );
}



function start_el(&$output, $item, $depth, $args) {
if ( $depth == 0 && ( isset($this->_current_page->ancestors) && in_array($item->object_id, (array) $this->_current_page->ancestors) ) )
return;

parent::start_el($output, $item, $depth, $args);
}


function end_el(&$output, $item, $depth) {
if ( $depth == 0 && ( isset($this->_current_page->ancestors) && in_array($item->object_id, (array) $this->_current_page->ancestors) ) )
return;

parent::end_el($output, $item, $depth);
}
}




WP Answers comments:

Hi John,

Thanks a lot for your all your help so far, but both snippets you've posted are not working. They both list out every page in the navigation. Sorry if it seems like I'm being a pain in the a**. I don't mean to be, but the functions just aren't working.

I'll be back online in about 2 hours.


John Cotton comments:

Fair enough.

I've got my code running on a test site and it works perfectly so I must be misunderstanding what you want to do (or what data is behind it) and it's hard to picture it without seeing your site.

If you stick some lines like this:

$output.= "line X: ".$item->object_id;

just before each of the returns you might get some idea why it's not displaying anything on your setup.


JC


WP Answers comments:

Hi John,

The answer below is working perfectly for me. You stuck it out though, and I'd like to send you some money for your efforts. What is your e-mail address so I can send you some money via Paypal?

Thanks again for your hard work brother.