I am building a website that has a lot of pages. The client is not a big fan of the Wordpress menu builder works (in appearance -> menus), and we have come to the conclusion that the best way for the menu to work is for the top level pages to be placed inside of appearance -> menus manually, and for all of the children (and grandchildren etc...) to be listed automatically.
The Goal
I want the menu to display on the front end as if I had dragged every page into the menu in appearance -> menus, having all of the same classes and gaining the "current-menu-item" class and other similar classes as is appropriate. I also want to be able to use a custom nav walker to add classes and elements to the menu (using start_lvl
and start_el
).
The Problem
I have created a custom nav walker and the menu is working almost exactly as I want it to. The only issue is that only the 2nd level items (i.e. the pages underneath the top level items that are dragged into the menu in appearance -> menus) are outputting and being wrapped with a <div class="sub-menu">
as I have set in the start_lvl
function in my custom walker. I need the 3rd level items to display in a new <div class="sub-menu">
, but this is not happening.
My code (nav walker)
//custom function for selecting posts based on a page parent (ne' term_id)
function twk_get_children_by_id($parent_id, $post_type=MENU_CPT) {
$args = array(
'posts_per_page' => -1,
'post_type' => $post_type,
'post_parent' => $parent_id,
'orderby' => 'menu_order',
'order' => 'ASC'
);
return get_posts( $args );
}
//custom nav menu walker class for Take Action Dropdown
class twk_Walker_Nav_Menu extends Walker_Nav_Menu {
function walk( $elements, $max_depth) {
$args = array_slice(func_get_args(), 2);
$output = '';
if ($max_depth < -1) //invalid parameter
return $output;
if (empty($elements)) //nothing to walk
return $output;
$id_field = $this->db_fields['id'];
$parent_field = $this->db_fields['parent'];
// flat display
if ( -1 == $max_depth ) {
$empty_array = array();
foreach ( $elements as $e )
$this->display_element( $e, $empty_array, 1, 0, $args, $output );
return $output;
}
$top_level_elements = array();
$children_elements = array();
$grandchildren_elements = array();
foreach ( $elements as $e) {
if ( 0 == $e->$parent_field ) {
$top_level_elements[] = $e;
if ( $e->type=='post_type' && $e->object == MENU_CPT ) {
$child_posts = twk_get_children_by_id($e->object_id);
foreach ( $child_posts as $child ) {
$child = wp_setup_nav_menu_item($child);
$child->post_type = 'nav_menu_item';
$child->menu_item_parent = $e->$id_field;
$child->object = 'custom';
$child->type = 'custom';
$child->ID = $e->$id_field.$child->ID;
$children_elements[ $e->$id_field ][] = $child;
$children_elements_classes[] = $child;
}
}
}
else {
$children_elements[ $e->$parent_field ][] = $e;
}
}
if ( empty($top_level_elements) ) {
$first = array_slice( $elements, 0, 1 );
$root = $first[0];
$top_level_elements = array();
$children_elements = array();
foreach ( $elements as $e) {
if ( $root->$parent_field == $e->$parent_field ) {
$top_level_elements[] = $e;
if ( $e->type=='post_type' && $e->object == MENU_CPT ) {
$child_posts = twk_get_children_by_id($e->object_id);
foreach ( $child_posts as $child ) {
$child = wp_setup_nav_menu_item($child);
$child->post_type = 'nav_menu_item';
$child->menu_item_parent = $e->$id_field;
$child->object = 'custom';
$child->type = 'custom';
$child->ID = $e->$id_field.$child->ID;
$children_elements[ $e->$id_field ][] = $child;
$children_elements_classes[] = $child;
}
}
} else {
$children_elements[ $e->$parent_field ][] = $e;
}
}
}
//assigning the classes to our dynamically populated posts
if ( $children_elements_classes ) {
_wp_menu_item_classes_by_context($children_elements_classes);
}
foreach ( $top_level_elements as $e ) {
$this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
}
if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
$empty_array = array();
foreach ( $children_elements as $orphans )
foreach( $orphans as $op )
$this->display_element( $op, $empty_array, 1, 0, $args, $output );
}
return $output;
}
/*
* START ADD WRAPPER AROUND SUB MENU
*/
public function start_lvl( &$output, $depth = 0, $args = array() ) {
global $titleParent;
// GET VARIOUS OTHER THINGS
// $pageid = get_post_meta( $item->ID, '_menu_item_object_id', true );
// $background = get_field('background_color', $pageid);
$indent = str_repeat( '\t', $depth );
$output .= "\n";
$output .= $indent;
$output .= "<div class='sub-menu sub-menu--level-" . $depth . "'>";
$output .= "<a class='sub-menu__link' href='";
$output .= get_permalink( get_page_by_title( $titleParent ) );
$output .= "'>";
$output .= "<h3 class='title'>";
$output .= $titleParent;
$output .= "</h3>";
$output .= "</a>";
$output .= "<ul class='sub-menu__item-container has-columns'>";
}
public function end_lvl( &$output, $depth = 0, $args = array() ) {
$indent = str_repeat("\t", $depth);
$output .= "$indent";
$output .= "</ul>";
$output .= "</div>";
$output .= "\n";
}
public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
$t = '';
$n = '';
} else {
$t = "\t";
$n = "\n";
}
$indent = ( $depth ) ? str_repeat( $t, $depth ) : '';
$classes = empty( $item->classes ) ? array() : (array) $item->classes;
$classes[] = 'menu-item-' . $item->ID;
$args = apply_filters( 'nav_menu_item_args', $args, $item, $depth );
// JJ - adding 'menu-item-has-children' class if applicable
$itemChildren = get_pages( array( 'child_of' => $item->object_id ) );
$itemHasChildren = (count( $itemChildren ) > 0) ? true : false;
$itemHasChildrenClass = ($itemHasChildren === true) ? ' menu-item-has-children' : '';
$class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );
$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . $itemHasChildrenClass . '"' : '';
$id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args, $depth );
$id = $id ? ' id="' . esc_attr( $id ) . '"' : '';
$output .= $indent . '<li' . $id . $class_names . '>';
$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, $depth );
$attributes = '';
foreach ( $atts as $attr => $value ) {
if ( ! empty( $value ) ) {
$value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
$attributes .= ' ' . $attr . '="' . $value . '"';
}
}
/** This filter is documented in wp-includes/post-template.php */
$title = apply_filters( 'the_title', $item->title, $item->ID );
$title = apply_filters( 'nav_menu_item_title', $title, $item, $args, $depth );
// JJ - check if page redirects to an external link
$openInNewTab = '';
$pagePostMeta = get_post_meta( $item->object_id );
if (array_key_exists('_redirector', $pagePostMeta)) {
if (strpos($pagePostMeta['_redirector'][0], 'url') !== false) {
$openInNewTab = 'target="_blank"';
} else {
$openInNewTab = '';
}
}
$item_output = $args->before;
$item_output .= '<a'. $attributes . ' ' . $openInNewTab . '>';
$item_output .= $args->link_before . $title . $args->link_after;
$item_output .= '</a>';
$item_output .= $args->after;
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
}
I think that the problem is caused by the fact that I am only getting the children of the top level items in my twk_get_children_by_id
function.
What I've tried
I have tried doing get_pages
instead of get_posts
in the twk_get_children_by_id
function, using the child_of
argument to get every level child page, but this results in level 2 and level 3 pages all being printed at the same level (i.e. under the first <div class="sub-menu">
element). I need the level 3 items to be separated from the level two ones.
I would really apprecatie help on this, and feel free to suggest a solution even if it is completely different to how I'm currently attempting to do it!
wp_list_pages()
instead of a nav menu. That way, it's all completely automated, and there isn't a menu sitting there for someone someday to discover and become perplexed when it doesn't work like other menus they've managed before.