Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

The goal: use wordpress's wp_nav_menu to generate a menu where child pages were dynamically added while keeping the correct submenu classes.

Before:

  1. Installations
  2. Habits

After:

  1. Installations
    • Roof
    • Garden
  2. Habits

Basically, I have a wp_nav_menu set up to show some pages (i.e. "Installations" & "Habits"). Either of those pages could have several child pages associated with them and I wanted my navigation to dynamically add those child pages into the menu, without using wp_list_pages. In my case, the nav was pulling from a custom post type ("actions"), but this can be customized.

share|improve this question
    
"This isn't a question..."-- then it should be split into a question and an answer. –  s_ha_dum Jun 25 '13 at 16:52
    
Hm, good point. That makes sense, I'll do that. Thanks. –  danbrellis Jun 25 '13 at 16:58
    
I just tried to split it, but it says I have to wait 7 hours to answer my own question. I'll fix it tomorrow. –  danbrellis Jun 25 '13 at 17:03
    
Must be your rep. Come back later. –  s_ha_dum Jun 25 '13 at 17:07
add comment

1 Answer

up vote 0 down vote accepted

My solution uses the Walker_Nav_Menu and a custom class and is mostly hacked together from this post: How do I dynamically populate wp_nav_menu from a custom taxonomy?

Solution:

//define the custom post type
//could use "page" or "post" as well.
define("MENU_CPT", "action");

//custom function for selecting posts based on a page parent (ne' term_id)
function hgs_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 title',
        'order'  => 'DESC'
    );                  
    return get_posts( $args );
}

//custom nav menu walker class for Take Action Dropdown
class HGS_Walker_Nav_Menu extends Walker_Nav_Menu {
    /**
     * Display array of elements hierarchically.
     *
     * It is a generic function which does not assume any existing order of
     * elements. max_depth = -1 means flatly display every element. max_depth =
     * 0 means display all levels. max_depth > 0  specifies the number of
     * display levels.
     *
     * @since 2.1.0
     *
     * @param array $elements
     * @param int $max_depth
     * @return string
     */
    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;
        }

        /*
         * need to display in hierarchical order
         * separate elements into two buckets: top level and children elements
         * children_elements is two dimensional array, eg.
         * children_elements[10][] contains all sub-elements whose parent is 10.
         */
        $top_level_elements = array();
        $children_elements  = array();
        foreach ( $elements as $e) {
            if ( 0 == $e->$parent_field ) 
            {
            //var_dump($e); //for troubleshooting
                $top_level_elements[] = $e;
                if ( $e->type=='post_type' && $e->object == MENU_CPT ) {

                    $child_posts = hgs_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;
            }
        }

        /*
         * when none of the elements is top level
         * assume the first one must be root of the sub elements
         */
        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 = hgs_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;
                }
            }
        }

        //assing 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 we are displaying all levels, and remaining children_elements is not empty,
         * then we got orphans, which should be displayed regardless
         */
        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;
    }
}

We implement the menu like so:

<?php wp_nav_menu( array(
                        'theme_location'  => 'nav-right',
                        'menu'            => 'Right Navigation Column',
                        'container'       => 'div',
                        'container_class' => 'nav-inner',
                        'container_id'    => '',
                        'menu_class'      => 'menu',
                        'menu_id'         => '',
                        'echo'            => true,
                        'items_wrap'      => '<h2>Take Action</h2><ul class="%2$s">%3$s</ul>',
                        **'walker'        => new HGS_Walker_Nav_Menu**
                    ) ); ?>

So I hope this helps someone. It is certainly very customizable if you are using various post types in a single menu as well by running a condition on $e->type and $e->object.

If anyone has more insight or some thoughts on how to improve this, please share.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.