Wordpress - Creating my own Menu

Introduction

As part of the introduction of WordPress as the primary engine for my site, I have had to set up a template. However, WordPress is not the only element of my site, and I expect to also have to include (at minimum) the following:-

  • An instance of GitWeb – to display my software repositories
  • Demonstrations of my applications
  • An issue tracking system (possibly combined with a discussion forum)

Each of these elements will want to take charge of the Content of the Web Pages they are controlling, but I expect all of them to exhibit the same look and feel for the basic web page. As with WordPress, the way I achieve this is via a site template.

However, this template system is not quite the same the approach in WordPress. It uses the following approach.

The application page (normally index.php within the application directory – although it could be any other page of the application too) will typically provide its contents through call backs – thus

if ( ! function_exists( ‘site_get_head_content’ ) ) :
function site_get_head_content() {
?><meta charset="<?php bloginfo( ‘charset’ ); ?>" />
<link rel="profile" href="http://gmpg.org/xfn/11" /><?php
}
endif;

And at the end of the page include

require($_SERVER[DOCUMENT_ROOT]./inc/template.inc);

and it is also possible to include a header or footer only template in its place.

One of the things this template does is it already outputs the <ul id=”main”> tag and the first <li> tag linking to the home page. The callback routine only requires the <li> tags for the other menu items. This is not how standard WordPress menus work.

A further complication is that the menu system used also supports multiple level tree structure to the menu, but that in order to do so, requires the parent tags that have children to have a special class added to them to indicate this.

Providing a menu to meet template requirements

Overview

The template requires that I have a function called site_get_menu() which outputs the html for the primary navigation menu for my site. A typical wordpress theme (as is done in twentyten for instance) would output the menu via wp_nav_menu() (with some other parameters), but this assumes it is in sole charge and insists on including the top level <ul> tag. It means I can’t use it.

I realised I would have to write my own menu handling system, but I still wanted the flexibility of configuring precisely what my menu holds using the WordPress Menu Administration. It turns out that by creating my own Walker Class extended from the WordPress Walker Class that it can be quite easy.

The Basic Menu Handling Function

I created the required site_get_menu() function inside my themes function.php file. In the end it turned out to be quite a simple piece of code.

function site_get_menu() {
  $locations = get_nav_menu_locations();
  if( isset( $locations[ primary] )) {
    $menu = wp_get_nav_menu_object( $locations[ primary ] );
    $items = wp_get_nav_menu_items($menu->term_id);
    $sorted_menu_items = array();
    foreach ( (array) $items as $key => $menu_item )
      $sorted_menu_items[$menu_item->menu_order] = $menu_item;
    unset($menu_items);
    $walker = new czen_menu_walker();
    echo $walker->walk($sorted_menu_items,0);
    unset($sorted_menu_items);
  }
}

We first need to check that the location I had previously registered (primary) actually had a menu in it that I could use, but once that is found, it is relatively easy to get the menu object and extract the menu items from it.

I needed to sort these menu items and which is done relatively easily with the foreach loop and then the crux of the output is to create and then call the my special Walker.

The Walker Class

I derived this class by copying the similar one from inside WordPress itself. Before I list it all there are a couple of comments to make.

Firstly, the WordPress Walker class expects that you add the markup for the new child level to the elements that make up the submennu. But for my requirements, I needed to add the class marker to the parent. This (amongst other things) allows me to add a small arrow to the parent menu that indicates it has children.

Secondly, the WordPress class I copied was attempting to control the tab indent via the depth parameter (outputing the number of tabs that correspond to the depth). However in my markup (and I suspect most peoples) there are two sets of indentation – one for the <ul> tag and a deeper indentation for the <li> tag. You will see that in the results of the code.

class czen_menu_walker extends Walker {
  var $tree_type = array( post_type, taxonomy, custom );
  var $db_fields = array( parent => menu_item_parent, id => db_id );
  var $previous_el = ; //Holds string of previous element, awaiting a decision on if there is sub level
  /**
  * @see Walker::start_lvl()
  * @since 3.0.0
  *
  * @param string $output Passed by reference. Used to append additional content.
  * @param int $depth Depth of page. Used for padding.
  */
  function start_lvl(&$output, $depth) {

    if ($depth == 0) {
      $output .=  class="down";
    } else {
      $output .=  class="right";
    }
    //We now know we are in a sublevel, so output the previous element from the class to where the next level starts

    $output .= $this->previous_el."\n";
    $this->previous_el = ; //And say its done

    $indent = str_repeat("\t", 2*$depth+3);
    $output .= "$indent<ul>\n";
  }

  /**
  * @see Walker::end_lvl()
  * @since 3.0.0
  *
  * @param string $output Passed by reference. Used to append additional content.
  * @param int $depth Depth of page. Used for padding.
  */
  function end_lvl(&$output, $depth) {
    $indent = str_repeat("\t", 2*($depth+1)+1);
    $output .= "$indent</ul>\n";
    $indent = str_repeat("\t", 2*($depth+1));
    $output .= "$indent";
  }

  /**
  * @see Walker::start_el()
  * @since 3.0.0
  *
  * @param string $output Passed by reference. Used to append additional content.
  * @param object $item Menu item data object.
  * @param int $depth Depth of menu item. Used for padding.
  * @param int $current_page Menu item ID.
  * @param object $args
  */
  function start_el(&$output, $item, $depth) {

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

    $indent = str_repeat( "\t", 2*($depth)+2);
    $output .= $indent . <li;

    if($depth == 0) {
      //we are at top level, so any child exists indication will be later
      $output .= ><a. $attributes .><span;
    } else {
      $this->previous_el .= ><a. $attributes .><span;
    }

    $this->previous_el .= >.apply_filters( the_title, $item->title, $item->ID );
    $this->previous_el .= </span></a>;
  }

  /**
  * @see Walker::end_el()
  * @since 3.0.0
  *
  * @param string $output Passed by reference. Used to append additional content.
  * @param object $item Page data object. Not used.
  * @param int $depth Depth of page. Not Used.
  */
  function end_el(&$output, $item, $depth) {
    $output .= $this->previous_el."</li>\n"; //previous el will only have anything in it if there were no children (start_lvl cleared it otherwise)
    $this->previous_el = ;
  }
}

So there you have it – the most interesting thing is that it partially outputs a set of menu elements. In my menu system the top level is slightly different to the lower levels – on the top level I add the class “drop” to the <span> tag, in the lower levels I add the class “right” to the <li> tag. So I write out in start_el() up to the point where you have to either add a class to indicate a submenu or not and put the rest of the element in the class variable $previous_el. When we reach the start_lvl() function, I can then add the appropriate class attribute and complete the element from the $previous_el variable, before starting on the <ul> tag that starts the next level.

The sublety here is that end_el() will output $previous_el if there have been no children, but if there have been then $previous_el will have been set to the zero length string when it was output, leaving end_el() with nothing to do.

Conclusion

There you have it, a nicely working menu system that uses WordPress Menu Admin to create the menus, but uses the Site Wide template system to manage part of the menu. I hope this is useful to someone. Feel free to copy the code.