WordPress Category Menus

I just wrote a small hack that displays, in the menu, a hierarchical menu of Categories. When you add Category to a menu, that category, and all it’s descendents, are rendered as a menu.

The plugin is here at my GitHub: WP Category Menus.

There are no settings.

Implementation Ideas, and the Actual Implementation

There were a few ways to create this feature:

  • function that copies a Category hierarchy into the menu hierarchy
  • a menu type for Category hierarchies
  • code that patches menu-drawing so it shows the Category hierarchy

I implemented the last option, mainly because it didn’t involve adding extra tasks for the users. It also doesn’t duplicate data: duplicated data is the bane of system admins.

The Walker and Menu Walker

The Walker is the base class for the Walker_Nav_Menu, which renders the navigation menu.

We extend the Walker_Nav_Menu class, to render our menus.

This new class, JK_Walker_Nav_Menu, is inserted into the system with the wp_nav_menu_args hook, which allows you to specify a walker to use instead of Walker_Nav_Menu.

The Walker class is similar to the Visitor pattern in GOF. The Walker takes care of traversing a hierarchy, depth first, calling a function at each node.

The Menu Walkers render hierarchies into HTML, using the UL and LI tags. HTML requires opening and closing tags to indicate what is “inside” an object, so these walkers call four functions: start_lvl(), end_lvl(), start_el(), end_el().

LVL means Level, and EL means Element. Here’s how the functions print out HTML:

<ul>  <!-- emitted by start_lvl -->
    <li>  <!-- emitted by start_el -->
    </li> <!-- emitted by end_el -->
</ul>  <!-- emitted by end_lvl -->

The *lvl functions are called when we descend a level, and the *el functions are called at each node.

Inversion of Control Containers

Walkers follow the “Hollywood Principle” of, “don’t call us. We’ll call you.” The Walker base class contains code to traverse the hierarchy. The child classes implement the methods that are called during the traversal. This is the inversion of control container pattern.

With IOC, the programmer is relieved of needing to write the traversal code. (Instead, the programmer then starts to wonder if there’s a way to shoehorn more logic into the functions that are called by the Walker. 🙂 )

To extend the built-in Walker_Nav_Menu class, you override the existing methods with your own code. In this case, I overrode only one function start_el().

The new start_el() function tests each object to see if it’s a category. If it isn’t a Category, we call the parent class’ version of start_el(). If it is, then we call a function that will insert a Category Menu.

That function, emit_category_hierarchy(), also implements a kind of walker, to traverse the Category hierarchy, instead of the menu hierarchy.

At this point, I started to have doubts: perhaps I’m duplicating code unnecessarily; perhaps I could create another class to traverse the Category hierarchy, or maybe one already exists. In the interest of keeping this a quick hack, I shelved those ideas.

While there was some duplication, I thought it was easier this way. The emit_category_hierarchy() function calls a few new functions: start_category_el(), get_term_children(), and recursively calls emit_category_hierarhcy(). It also calls the existing methods: start_lvl(), end_lvl(), and end_el(). It’s pretty “mixed up” in there.

Odds and Ends

start_category_el() is almost the same as start_el(), but because we’re traversing the Category tree rather than a Menu tree, it’s calling different functions to extract the data to present. I just copied the original code and edited.

The JK_Walker_Nav_Menu->get_term_children() is like WP’s get_term_children(), except mine returns only one level of children. WP’s returns all the child terms and flattens them into a one-dimensional array. So the code, again, is lifted from WordPress, but edited to get only the current branch.

Finally, the code doesn’t implement the filters that the parent class does. That’s bad form… but this is just a hack.

Leave a Reply