Today I sat down to convert a static theme consisting of nothing more than HTML and CSS into a WordPress theme, complete with calls to functions like wp_nav_menu() for the construction of the menu’s. For this particular theme I began with a copy of the TwentyTen theme. When I came across the task of establishing a navigational menu I found some rather unexpected results from WordPress – something I think they should consider changing in future releases. (Note: The following assumes version 3.1.3 is being used)
When you open up header.php in the TwentyTen theme you’ll find the following on or around line 85:
<?php wp_nav_menu( array( 'container_class' => 'menu-header', 'theme_location' => 'primary' ) ); ?>
This is really pretty straight forward. It defines itself as the location for any menu assigned to the “primary” spot. Additionally, it sets a container class to “menu-header”. Given this information, we should expect this to result in is an unordered list wrapped in a div (the default container is a div) that has the class “menu-header”. So what markup is generated by WordPress when somebody sets up a fresh copy of it and checks out their TwentyTen theme?
<div class="menu"> <ul> <!-- ... --> </ul> </div>
At first there may not appear to be any problems, but look more closely at the container class declared in the call to wp_nav_menu() and what is actually applied in the generated markup. While we declared a class of “menu-header”, what we actually get is a class of “menu”. This really frustrated me this evening.
Some users of WordPress have been unable to reproduce this effect, and the reason is fairly simple why they couldn’t – they likely have a menu already assigned to the “primary” location. Anybody who does not initially have a menu created and applied to this location will be left scratching their head.
So what is WordPress doing exactly? Initially WordPress will look for a menu assigned to this location, if it doesn’t find one it will move on to do something else. Along down the line, just before throwing its hands up completely it will call a fallback method to generate something for the user. The default fallback method is provided for us in the source (line 141):
$defaults = array( /* ... */ 'fallback_cb' => 'wp_page_menu' /* ... */ );
So if no menu is present when wp_nav_menu() is called, it will fallback into calling the wp_page_menu() method. Up to this point this shouldn’t seem like much of a threat. Sure, if I don’t have a menu WordPress can go ahead and generate a list of pages that I might have published. Unfortunately, this is where some communication-breakdown occurs and things get a bit messy.
Recall the generated HTML list previously shown, this is what wp_page_menu() generated for us in the absence of a user-defined menu. Like wp_nav_menu(), wp_page_menu() has its own set of internally-declared default arguments that will determine much of how the results will look and feel. We can see these defaults declared below:
$defaults = array( 'sort_column' => 'menu_order, post_title', 'menu_class' => 'menu', 'echo' => true, 'link_before' => '', 'link_after' => '' );
The only thing we’re interested in right now is the ‘menu_class’ which, by default, is set to “menu”. This is where the “menu” class comes from in the place of our “menu-header” class passed to wp_nav_menu(). This isn’t the light at the end of the tunnel, things get even messier from here.
While it’s nice of WordPress to generate a list for us in the absence of a menu, the integrity of our data breaks down in the process. When we call wp_nav_menu() and tell it to set the “container_class” to “menu-header”, the resulting call to wp_page_menu() completely ignores this request. Again, this isn’t the end of the mess, the wp_nav_menu() function also allows us to provide a ‘menu_class’, or a class which should be applied to the generated menu within the container. Suppose we did this:
<?php wp_nav_menu( array( 'container_class' => 'menu-header', 'menu_class' => 'foo', 'theme_location' => 'primary' ) ); ?>
What we should expect to see from this is a menu that looks like the following:
<div class="menu-header"> <ul class="foo"> <!-- ... --> </ul> </div>
This should be the result regardless if pages or custom menu items populate the UL. If no menu is present, and WordPress falls back on the wp_page_menu() function, what it generates is anything but expected:
<div class="foo"> <ul> <!-- ... --> </ul> </div>
Note how the “menu_class” is actually applied to the container, and the “container_class” of “menu-header” is entirely abandoned. This isn’t what we asked for.
If we turn around and create a menu called “Sample Menu”, and set it to be displayed in the “primary” location, what we are presented with is the following:
<div class="menu-header"> <ul id="menu-sample-menu" class="foo"> <!-- ... --> </ul> </div>
Notice how it gets the container class correct, and the menu class correct when we actually have a menu for it to display.
I’m hoping this problem will be resolved in future versions of WordPress for the sanity of those creating themes. Not for them alone, but for the reputation of WordPress as a whole too. If its own internal communication isn’t consistent, it makes for a very bad testimony when those of us out in the real world are attempting to act as evangelists for the wonderful platform.