One feature of WordPress that I’ve never quite been happy with is the wp_nav_menu function. It has a lot of customizable options but one thing I think could be improved is its support for multiple levels (drop-downs.)
While working on a website for a client, we needed to add an arrow next to top-level nav that had children. We needed to:
- add a “dropdown” class to the top-level nav, and
- add <b class=”caret”></b> inside the link (yes, that’s bootstrap-inspired)
wp_nav_menu doesn’t support either. After some brainstorming, I had three possible ideas:
- Buffer the wp_nav_menu output (aka set the “echo” flag to false) and use regex to parse the HTML and add what we need
- Make use of a PHP class like DOMDocument to traverse the HTML and add what we need
- Write a custom walker and add what we need before we ever call wp_nav_menu
I’ve been looking for an excuse to get into further WordPress development so I decided to use a custom walker.
Modifying the Walker
I couldn’t find muchuseful information pertaining to my need so I dug into the WordPress core and found two files in /wp-includes, class-wp-walker.php and nav-menu-template.php.
Through some trial and error I found that the display_element function in class-wp-walker.php would allow me to append the class I wanted. Here’s what that code looked like (placed in my theme’s functions.php):
That solved the first part of my problem, but the HTML modification issue had yet to be tackled. While experimenting, I came across the start_el function that looked like I could append some HTML. I copied the entire start_el function to functions.php and added the following just before the closing </a> tag:
It’s possible to search for the “dropdown” class because display_element is called before start_el.
Anyway, this worked like a charm but was uber bulky – I love simplicity. I finally had my “D’OH” moment when I realized that it’s possible to append my HTML directly in the display_element function. I was looking at start_el and noticed it calling $item->title. $item is the same as $element in display_functions, so I appended
to my custom display_element function and BINGO!
Here’s the final code (placed in the theme’s functions.php file):