Quantcast
Channel: Suzanne Ahjira» Tutorials
Viewing all articles
Browse latest Browse all 29

Create A Conditional Secondary Menu For Genesis Child Themes

$
0
0

If you’d like to maintain one master menu for your site, but break it up across your Genesis Primary and Secondary menu areas, showing top-level items in the Primary and their conditional sub-menu items in the Secondary, you’re at the right place.

Conditional secondary menus can be very useful on sites with a significant amount of hierarchical content. They help the user explore and find content more easily, and if styled with current page indicators, they can help you know where you are within the site as well. I’ve seen a few solutions around the net to make your secondary Genesis menu conditional based on the selected item in the Primary menu, but none of them worked the way I wanted or needed. Most use the page hierarchy only (Bill Erickson’s Genesis Subpages as Secondary Menu plugin for example) and they circumvent the WordPress Menu system all together. That’s limiting and inconvenient.

Genesis Primary Menu and Secondary Menu Area Assignment

I wanted to manage my menu items using the WordPress menu system so that I could have categories and custom links in my menus, not just pages. The problem with this when it comes to creating a conditional secondary menu is that, by default, the secondary menu has no awareness of the primary menu and therefore can’t display a different menu based upon the item you clicked in the Primary. You can write a function to do that, but who wants to keep track of and manage all their menu items in a custom function? Nope give me something simple and let me manage my menus as I normally would.

The only solution I could think of to solve all of my requirements was to create one master menu with top-level and sub-menu items arranged as I wanted, and then to apply that one menu to both the Primary and Secondary menu areas, somehow controlling the display of the items so that only the top-level items displayed in the Primary area and only sub-menu items displayed in the Secondary menu area. When the single menu is arranged this way and then used in both locations, both menus always have awareness of which page you’re viewing and its relationship to a parent item. It’s only a matter of controlling the display of the items.

At the time of writing, this menu system is active on this site, but in case we’re now in the future and I’ve redesigned my site again (likely), then this should help.

Pasted_Image_6_25_13_5_20_PM

In the image above, you’ll see both the Primary menu and the Secondary menu are visible. I’ve clicked the Primary menu item named Flint and the Secondary menu then displayed my secondary menu items for Flint. Some of these secondary items are pages, but some are category archive links. The Forums item is a custom link to my bbPress installation. This is possible because, again, I’m using the WordPress menu system.

Now let’s see what’s in the Bookmarks secondary…

Screenshot_6_25_13_7_43_PM-3

In this case, I’m displaying all my sub-categories for my parent category Bookmarks, also possible because I’m using the WordPress menu system. This would not be possible if I had to rely on the page hierarchy alone. You could create pages for every sub-category and then write custom page templates for each one, to show the post lists, but hello… what? Sigh. We want simple.

Current Ancestor Page Indication

Notice in both cases that I’ve styled the Primary menu item with some special styles that clearly indicate it’s the parent, or ancestor, of the page we’re viewing. This is possible because again, we’re technically displaying one menu here so even if I click Flint’s News item in the Secondary menu, the Flint menu item in the Primary will have the list item class current-menu-ancestor which makes this possible.

The screenshot to the right is part of my menu from the Appearance » Menus page. As you might have guessed, the top-level and sub-menu items are all arranged into one master menu. Also notice that I have some grandchild level pages there. I do this so that when viewing those pages, the top-level page is styled with the current page styles.

That’s the preface to this solution. We want to be able to manage our menus from the WordPress menus page because here we can use pages, categories, custom post types, custom taxonomies and custom links. We can also easily manage their relationships to each other and we make use of WordPress’s native menu classes as well. Now the only thing left is to figure out how to only show top-level items in the Primary menu and only secondary level items in the Secondary menu.

Apply This Code To Functions.php

It took me a while to assemble this solution and I’m still not sure this is the best way to achieve the results I wanted, but it works and it’s fairly simple. First let’s look at the code.

add_filter( 'genesis_do_nav', 'ahjira_nav_args', 10, 3 );

function ahjira_nav_args( $nav_output, $nav, $args ) {
  if( $args['theme_location'] == 'primary' ) {
    $args['depth'] = 1;
  }
  $nav = wp_nav_menu($args);
  $pattern = genesis_markup( '<nav class="primary">%2$s%1$s%3$s</nav>', '<div id="nav">%2$s%1$s%3$s</div>', 0 );
	$nav_output = sprintf( $pattern, $nav, genesis_structural_wrap( 'nav', 'open', 0 ), genesis_structural_wrap( 'nav', 'close', 0 ) );
  return $nav_output;
}

Here we’re running a filter every time the page loads. This filter programmatically limits the output for the Primary menu area to only top-level items (depth 1). In one earlier incarnation of this solution, I was using CSS to do this, but I prefer more efficient code so I chose this solution. This will prevent your Primary menu from generating drop down sub-menus.

Extra Credit For The Obsessive Types

Here’s an extra – optional – filter some of you might want to include. It’s designed to apply the current-menu-item class to a specified menu item in the event your conditions are true.

add_filter( 'nav_menu_css_class', 'ahjira_flint_class' , 10 , 2);

function ahjira_flint_class($classes, $item){
  if( is_tag() || is_home() ) {
    // these are the conditions where we want no current page indication in the primary menu
  } elseif( ( is_singular( 'topic' ) && $item->title == "Flint" ) || 
      ( bbp_is_single_user() && $item->title == "Flint" ) || 
      ( in_category( 'status' ) && $item->title == 'Status' ) ||
      ( in_category( 'flint' ) && $item->title == 'Flint' ) ||
      ( in_category( 'updates' ) && $item->title == 'Flint' ) ||
      ( in_category( 'skins' ) && $item->title == 'Flint' ) ||
      ( in_category( 'art' ) && $item->title == 'Art' ) ||
      ( in_category( 'bookmarks' ) && $item->title == 'Bookmarks' ) ||
      ( in_category( 'tutorials' ) && $item->title == 'Tutorials' ) ||
      ( in_category( 'plugins' ) && $item->title == 'Plugins' )
    ) {
    $classes[] = "current-menu-ancestor";
  }
  return $classes;
}

This second filter is not necessary unless you want to style your Primary menu items to show when they are the current page ancestor for the page you’re currently viewing. In some cases this occurs naturally because of the way we’ve set up our master menu, but I also wanted my menu to show current ancestor status when a few other scenarios were active as well. For example, consider single posts. If I’m viewing the Bookmarks category archive, the Bookmarks menu item in the Primary menu will logically display as the current menu item. It will also do that if I’m viewing the archive of any Bookmarks sub-category. But what if I’m viewing a post from the Bookmarks category? In that case, the menu loses awareness of where we’re at in the site and won’t display the Bookmarks item as the current menu ancestor. This filter solves that problem.

Within the function, you see a number of OR scenarios… OR this OR this OR this, etc. Examples include:

( bbp_is_single_user() && $item->title == "Flint" )

( in_category( 'flint' ) && $item->title == 'Flint' )

( in_category( 'plugins' ) && $item->title == 'Plugins' )

These conditions are doing two things, they’re checking to see if the page or post we’re viewing is either in the specified category or is a certain kind of page and also whether a Menu item with the specified title exists. The first example above checks to see if we’re on the profile page of a forum user and whether a menu item name Flint exists. If both conditions are true, this function adds the current-menu-ancestor class to the Flint menu item, which then makes way for the CSS to display it as the parent item.

In the case of the home page or if we’re viewing a tag archive, we simply return without flagging any menu item as the current parent. The home page is the parent of them all and tag archives aren’t represented in my menus so it makes no sense to style a menu item as the current parent in those cases.

* Note that the second function there is unique and tied to my site and my page names and my category names, etc. You would have to completely rewrite that function to meet your own needs.

Add Some CSS And You’re Good To Go

Once you have your menu created and applied to both the Primary and Secondary menu areas, and you’ve applied the functions above to your functions.php file, then all you really need is some CSS to wrap this up. Let’s see what it takes.

/* Prep Primary div to display like tabs */
#nav {
  background: #F6F6F6 url(images/pixel.png) bottom left repeat-x;
  padding: 5px 0 0;
}

/* Style the current menu item in the Primary menu */
#nav .genesis-nav-menu li.current-menu-item a,
#nav .genesis-nav-menu li.current-menu-ancestor a {
  background: white;
  border: 1px solid #E1E1E1;
  border-bottom: none;
}

/* Set a minimum height for Secondary in the event we have no items to show */
#subnav {
  background: white;
  border-bottom: 1px solid #E1E1E1;
  min-height: 8px;
}

/* Hide everything in Secondary so we can show only the sub-menus later */
#subnav .genesis-nav-menu li.menu-item,
#subnav .genesis-nav-menu li.current-menu-item ul li ul,
#subnav .genesis-nav-menu li.current-menu-ancestor ul li ul,
#subnav .genesis-nav-menu li.current-menu-item a,
#subnav .genesis-nav-menu li.current-menu-ancestor a {
  display: none;
  width: auto !important; /* overrides fixed width sub-menus */
}

/* Reveal the sub-menu for the item selected in Primary ha! */
#subnav .genesis-nav-menu li.current-menu-item,
#subnav .genesis-nav-menu li.current-menu-ancestor {
  display: inline;
}

/* Make the sub-menu display correctly */
#subnav .genesis-nav-menu li.current-menu-item ul,
#subnav .genesis-nav-menu li.current-menu-ancestor ul {
  border: none;
  left: auto;
  position: relative;
  width: 100%;
}

/* Make the sub-menu items display correctly */
#subnav .genesis-nav-menu li.current-menu-item ul li,
#subnav .genesis-nav-menu li.current-menu-ancestor ul li,
#subnav .genesis-nav-menu li.current-menu-item ul li a,
#subnav .genesis-nav-menu li.current-menu-ancestor ul li a {
  border: none;
  display: inline-block;
}

Let’s just go through this quickly. The first selector for #nav pads the top of that div by 5px so that we have some space above our tabs. Adjust as you’d like. The background image however is something we need to note. If you want (what looks like) a bottom border on your Primary menu, you need to fake it with a background image rather than applying a true CSS border. This is because CSS borders are applied outside the div and that border will appear under our current menu item tab if we do it that way. We want our current tab to be seamlessly connected to our Secondary menu so we apply a 1 x 1 pixel image to the #nav div, positioned along the bottom and repeated horizontally to give the appearance of a border. If you don’t want a border you can remove the image.

Next we’re styling the current menu item for the Primary menu. Those of you using Flint can do most of that on the Flint » Menus page but if you want borders on your tab, you need to leave that CSS in place. You can remove the background declaration.

In the next piece for #subnav, we’re setting the background color, bottom border and min-height. Again, Flint users can set the background color and border style on the Flint » Menus page, but the min-height needs to remain if you want your Secondary menu to have a minimum height. This is nice in the event that you have no Secondary items for a Primary menu item.

For the section starting on line 22, we’re hiding everything in the Secondary menu and overriding any fixed width list items. We do this so that we can only show sub-menus in the Secondary. No top-level items allowed.

For the section starting on line 32, we cleverly show the list item that’s been flagged as the current menu item. Remember, we’re technically showing the exact same menu in both the Primary and Secondary areas. When I click Flint in the Primary area, there’s another, identical, Flint hiding in the Secondary that now has the class current-menu-item. We want to show that!

For the section starting on line 38, we’re un-styling the current menu item. We don’t want tabs in the Secondary menu, just plain links.

And in the last section starting on line 47, we’re ensuring that our sub-menu items no longer display one on top of the other, as in a drop down menu. We want them to display inline and without borders which may have been applied by your theme.

Wrap Up

Now, realize that copying and pasting verbatim will likely not produce exactly what you see on my site or in my screenshots. Some of my menu styles are in my main stylesheet and aren’t represented here, styles such as font size, list item link padding, etc. But this code and CSS should get you 95% of the way there. Good luck!


Viewing all articles
Browse latest Browse all 29

Trending Articles