1

I think I'm trying to do something unusual I guess because I can't find it anywhere.

I want to have a submenu base upon content off the associated page.
e.g. In my menu there is a menu-item called "TOP". This item has a subpage called "chapter 1".
On the page of "chapter 1" is a lot of content wich holds values seperated in H1 or H2 tags.

My goal is to have the H1 and H2 tags (including their value) loaded in the submenu of "chapter 1".

I was thinking to do this with the wp_nav_menu function and add a walker to it. The problem is that I have no idea where or how to start (especially with the walker). Obvious I have created the page and the submenu in the stylesheet like below:

div class="nav">
    <?php wp_nav_menu( array( 'theme_location' => 'menu') );?>
</div>

But now I need to load the content. I hope anyone can assist me with this "problem" on where to start.

  • EDIT -
    So I found a way to get only the content of all "H1" tags. this only is for the page that is currently being viewed.

    function getTextBetweenTags($tag, $html, $strict=0)
            {
                /*** a new dom object ***/
                $dom = new domDocument;
    
                /*** load the html into the object ***/
    
                $dom->loadHTML($html);
    
    
                /*** discard white space ***/
                $dom->preserveWhiteSpace = false;
    
                /*** the tag by its tag name ***/
                $content = $dom->getElementsByTagname($tag);
    
                /*** the array to return ***/
                $out = array();
                foreach ($content as $item)
                {
                    /*** add node value to the out array ***/
                    $out[] = $item->nodeValue;
                }
                /*** return the results ***/
                return $out;
            }
    
            global $post;
            $post_id = $post->ID;
            $html = get_post_field('post_content', $post_id);
            $content = getTextBetweenTags('h1', $html);
            $i=0;
            echo '<ul>';
            if($content[0]){ 
                foreach( $content as $item )
                {
                    echo '<li><a href="#'.$i++.'">'.utf8_decode($item).'</a></li>';
    
                }
            }else{
            echo'<li>'.get_the_title().'</li>'; 
            }
            echo '</ul>';
    
  • NEW EDIT - So I took on custom walkers. I now have taken a walker from a tutorial I found online. This walker takes the description from the wp_nav_menu and places it beneath the actual title. Here is the walker:

    class custom_walker extends Walker_Nav_Menu{        
    //start of the sub menu wrap
    function start_lvl(&$output, $depth) {
    $output .= '<div class="drop">
                    <div class="holder">
                        <div class="container">
                            <ul class="list">';
    }
    
    //end of the sub menu wrap
    function end_lvl(&$output, $depth) {
    $output .= '
                </ul>
            </div>
        </div>
        <div class="bottom"></div>
    </div>';
    }
    
    //add the description to the menu item output
    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 .'>';
    $item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
    $item_output .= '<br /><span class="sub">' . 
            $post_id = $post->ID;
            $html = get_post_field('post_content', $post_id);
            $content = getTextBetweenTags('h2', $html);
            $i=0;
            echo '<div style="background:#000000;"><ul>';
            if($content[0]){ 
                foreach( $content as $item )
                {
                    echo '<li><a href="#'.$i++.'">'.utf8_decode($item).'</a></li>';
                }
            }
            echo '<ul></div>'
    
     . '</span>';
    $item_output .= '</a>';
    $item_output .= $args->after;
    
    $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );}}
    

I replaced the part where the $description should be with my custom code to display the content between the H2 tags.

This works but here's the problem. I created a div with a black background so it's clear wich part is loaded. The div-blocks are on top of the menu and not beneath it. I can't determain wich block is witch what sub menu item.

The current script creates content for every submenu item. This is great however it only shows the content from the page where it's on. So I changed the $post->ID to $item->ID but that only gives me the ID's of the nav elements. The upside from this is that it shows content beneath the menu elements.

I think I'm on the right track here, but need a little help from my friends :-)

M.

-edit- This is the current start_el function

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

    $current_post = get_post( $item->object_id );

    $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 .= '<br /><span class="sub">'.
    $doc = new DOMDocument();
    $doc->LoadHTML( $post->post_content );
    $titles = $doc->getElementsByTagName('h1');
    foreach ($titles as $a_title) {
        $single_title = $doc->saveHTML( $a_title );
    }   
    '</span>';
    $item_output .= '</a>';
    $item_output .= $args->after;


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

Basically, you add a custom walker to wp_nav_menu. Whenever an item begins, you can check for the content of the page underlaying the navigation item.

When a content is available, read the content into a new DOMDocument-Object and filter out all titles. With the titles, create sub-links to the current link beeing made in the start_el-function:

Working example:

class Custom_Walker extends Walker_Nav_Menu {
        function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
            $output .= '<li>';

            // link attributes for the "normal" 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        ) .'"' : '';

            $output .= sprintf( '%1$s<a%2$s>%3$s</a>%4$s',
                $args->before,
                $attributes,
                apply_filters( 'the_title', $item->title, $item->ID ),
                $args->after
            );

            // Check for titles in the content and add them as a sub-ul after the current a-element
            // but only on level 3
            if ( $depth == 3 ) {
                $current_post = get_post( $item->object_id );
                if ( $current_post->post_type == 'page' ) {
                    $doc = new DOMDocument;
                    $doc->LoadHTML( $current_post->post_content );
                    $titles = $doc->getElementsByTagName( 'h1' );
                    // begin the sub-list here
                    $output .= '<ul>';
                    foreach ( $titles as $a_title ) {
                        // clean up the title to use as a fragment in the href-attribute
                        $sanitized_title = sanitize_title( $doc->saveHTML( $a_title ) );
                        // and add the link to the output as a li-element
                        $output .= sprintf( '<li><a href="%s#%s">%s</a></li>', esc_attr( $item->url ), $sanitized_title, $doc->saveHTML( $a_title ) );
                    }
                    // end of sub-list
                    $output .= '</ul>';
                    $doc = null;
                }
            }
        }

        function end_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
            $output .= '</li>';
        }
    }
  • Thnx for your reply. I don't really see how this works but that could just be me. I get the following error: Catchable fatal error: Object of class DOMDocument could not be converted to string – Interactive Nov 5 '14 at 14:16
  • How about $single_title = $doc->saveHTML( $a_title ) inside the foreach-loop? Just an idea, not tested. – uruk Nov 5 '14 at 14:21
  • Still remains but it could also be me I don't know if I implemented it right. I'll add a edit to the question – Interactive Nov 5 '14 at 14:27
  • Edited my answer above – uruk Nov 5 '14 at 14:52
  • DUDE. Sweet. Wow this is nice. I want this just one level deeper. The client is able to create a second level menu and the content on that page is what I want to have as a third level. If you know what I mean. This helped me a lot. Thank you so much. Let me know if you have an idea how to lower this with one level – Interactive Nov 5 '14 at 15:06

Your Answer

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

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