1

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!

  • Using a nav menu and then auto-adding children may be kind of counterintuitive for anyone who is used to managing the full menu in the Customizer or the other UI provided. If the menu should contain all pages, it might be better to use 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. – WebElaine Apr 24 at 16:52
  • @WebElaine I agree that using WP list pages would probably be better, but in this case the client still wants to be able to control which top level pages get added to the menu (as there are some pages that shouldn't be), and so the appearance -> menus functionality is still needed... I think... – JJ Gerrish Apr 25 at 8:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.