Customizing WordPress Navigation Items using a Walker
Using a fabulous WordPress plugin called External Links to put little arrow icons beside external links was the task this morning.
Installation and setup took less than 60 seconds and all worked well. But wait, there are arrow icons in my header navigation links.
Surely as per the documentation I can add the noicon
class to the WordPress header navigation items. Let’s try.
Nope! WordPress adds custom classes to the parent element <li>
tag and unfortunately not to the target <a>
tags.
So where do I start ‘enhancing’ code to make this work in a future-proof manner? Let’s start with the plugin. I not big on modifying other’s plugins willy-nilly for one-off functionality, but one idea is to make the plugin code look at the parent node and check if it has the noicon
class. The way the plugin is designed makes this infeasible in a short amount of time.
1 2 3 4 5 6 7 8 9 | if ( !$is_image && $this->opts['icon'] && !in_array('external_icon', $anchor['attr']['class']) && !in_array('no_icon', $anchor['attr']['class']) && !in_array('noicon', $anchor['attr']['class']) ) { // don't add an icon if there is no text in the link if ($anchor['body'] != null) { $anchor['attr']['class'][] = 'external_icon'; $updated = true; } } |
I could possibly ‘cheat’ and add some CSS rules like this:
1 2 3 4 5 6 7 8 9 10 11 | /* Plugin's CSS */ a.external_icon { background: url(external.png) center right no-repeat; padding-right: 13px; } /* Overriding CSS */ .noicon a.external_icon { background: inherit; padding-right: inherit; } |
But that’s not me! Let’s be programmatic and hook into a WordPress function. Long story short, looking through the source code of WordPress reveals several filters related to menu items, but none are fine-grained enough to modify the anchor tags individually. However, there is a ray of light. The docs for wp_nav_menu()
state that it is possible to pass in a custom Walker_Nav_Menu
class. Here is the pertinent code from the base walker class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) { $indent = ( $depth ) ? str_repeat( "\t", $depth ) : ''; $classes = empty( $item->classes ) ? array() : (array) $item->classes; $classes[] = 'menu-item-' . $item->ID; /* ... snip ... */ $id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args, $depth ); $id = $id ? ' id="' . esc_attr( $id ) . '"' : ''; $output .= $indent . '<li' . $id . $class_names .'>'; /* Here! */ $atts = array(); $atts['title'] = ! empty( $item->attr_title ) ? $item->attr_title : ''; $atts['target'] = ! empty( $item->target ) ? $item->target : ''; $atts['rel'] = ! empty( $item->xfn ) ? $item->xfn : ''; $atts['href'] = ! empty( $item->url ) ? $item->url : ''; |
Let’s extend the base walker class, add the class noicon
to the anchors, and pass that class into wp_nav_menu()
like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | if ( ! class_exists( 'Header_Nav_Walker' ) ) : class Header_Nav_Walker extends Walker_Nav_Menu { /** * Starts the element output. * * @since 3.0.0 * @since 4.4.0 The {@see 'nav_menu_item_args'} filter was added. * * @see Walker::start_el() * * @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 array $args An array of wp_nav_menu() arguments. * @param int $id Current item ID. */ public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) { $indent = ( $depth ) ? str_repeat( "\t", $depth ) : ''; $classes = empty( $item->classes ) ? array() : (array) $item->classes; $classes[] = 'menu-item-' . $item->ID; /* SNIP */ $atts = array(); // DRAKEN - add this class to the link to avoid adding an external icon $atts['class'] = 'noicon'; $atts['title'] = ! empty( $item->attr_title ) ? $item->attr_title : ''; $atts['target'] = ! empty( $item->target ) ? $item->target : ''; $atts['rel'] = ! empty( $item->xfn ) ? $item->xfn : ''; $atts['href'] = ! empty( $item->url ) ? $item->url : ''; /* SNIP */ $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args ); } } endif; |
1 | wp_nav_menu( array( 'theme_location' => 'header-menu', 'menu_id' => 'header-menu', 'walker' => new Header_Nav_Walker ) ); |
And I’m done!