Adding custom classes to Soil nav walker

I’m want to add the BEM naming convention to my Sage project through the nav-walker plugin file but I’m not seeing my changes when I save. Am I not seeing changes because it’s a plugin? And what would be the best way to add classes to the ‘ul’, ‘li’, ‘a’, and dropdown items? I had another walker function that worked with the previous version of Roots but now that doesn’t work and I can’t seem to get passed all the errors I’m encountering when trying to implement it.

Does anyone have a working walker function like this or the knowledge to help me out? Thanks

2 Likes

Figured it out… wasn’t using the right namespaces. Still getting used to those. Here’s a gist for future reference though: https://gist.github.com/coreybruyere/0174a0c5078a0887d3ee

A bit late to the game here but how do you add this to the frontend? At the moment I’m doing this in my templates/header.php:

wp_nav_menu( array( 'theme_location' => 'primary_navigation', 'walker' => new NavWalker ) );

But I’m getting this PHP error Fatal error: Class 'NavWalker' not found

Any solutions to this?

@nathansmithdeveloper, at the top of your header.php you will have to declare the namespace that NavWalker is in, i.e.

<?php

use Roots\Soil\Nav;

// Other header jazz

wp_nav_menu( array(
  'theme_location' => 'primary_navigation',
  'walker' => new NavWalker(),
) );

Assuming you’ve used the navwalker referenced in coreybruyere’s Gist, and that you have included the gist in your functions.php file

1 Like

Thanks for the tip, it’s still not working though!

I should mention that I have Soil installed (v 3.6.2), and I got a redeclare error so I commented out this line add_theme_support('soil-nav-walker'); in lib/setup.php.

I added the gist code to the bottom of lib/customizer.php

And I’m still getting the same error of Fatal error: Class 'NavWalker' not found!

Assuming the customizer.php file is in it’s original form adding the gist provided wouldn’t work, this is because the decleration of namespace i.e. namespace Roots\Soil\Nav; would need to be at the top of the customizer.php file.

Assuming you’re using the latest version of Sage try this:

Create a new file in sage/lib called custom-navwalker.php
In sage/functions.php add the new custom-navwalker.php file to your assets array like so:

$sage_includes = [
  'lib/assets.php',    // Scripts and stylesheets
  'lib/extras.php',    // Custom functions
  'lib/setup.php',     // Theme setup
  'lib/titles.php',    // Page titles
  'lib/wrapper.php',   // Theme wrapper class
  'lib/customizer.php', // Theme customizer
  'lib/custom-navwalker.php', // Custom Navwalker
];

In your custom-navwalker.php file include the contents of the gist like so (notice I’ve adjusted the namespace to avoid soil conflicts):

<?php

namespace Roots\Soil\CustomNav;

use Roots\Soil\Utils;

/**
 * Cleaner walker for wp_nav_menu()
 *
 * Walker_Nav_Menu (WordPress default) example output:
 *   <li id="menu-item-8" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-8"><a href="/">Home</a></li>
 *   <li id="menu-item-9" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-9"><a href="/sample-page/">Sample Page</a></l
 *
 * NavWalker example output:
 *   <li class="menu-home"><a href="/">Home</a></li>
 *   <li class="menu-sample-page"><a href="/sample-page/">Sample Page</a></li>
 *
 * You can enable/disable this feature in functions.php (or lib/config.php if you're using Sage):
 * add_theme_support('soil-nav-walker');
 */
class NavWalker extends \Walker_Nav_Menu {
  private $cpt; // Boolean, is current post a custom post type
  private $archive; // Stores the archive page for current URL

  public function __construct() {
    add_filter('nav_menu_css_class', array($this, 'cssClasses'), 10, 2);
    add_filter('nav_menu_item_id', '__return_null');
    $cpt           = get_post_type();
    $this->cpt     = in_array($cpt, get_post_types(array('_builtin' => false)));
    $this->archive = get_post_type_archive_link($cpt);
  }

  public function checkCurrent($classes) {
    return preg_match('/(current[-_])|active/', $classes);
  }

  // @codingStandardsIgnoreStart
  public function display_element($element, &$children_elements, $max_depth, $depth = 0, $args, &$output) {
    $element->is_subitem = ((!empty($children_elements[$element->ID]) && (($depth + 1) < $max_depth || ($max_depth === 0))));

    if ($element->is_subitem) {
      $element->classes[] = 'dropdown';

      foreach ($children_elements[$element->ID] as $child) {
        if ($child->current_item_parent || Utils\url_compare($this->archive, $child->url)) {
          $element->classes[] = 'active';
        }
      }
    }

    $element->is_active = strpos($this->archive, $element->url);

    if ($element->is_active) {
      $element->classes[] = 'active';
    }

    if ($depth === 0) {
      $element->classes[] = 'nav__item';
    } else {
      $element->classes[] = 'nav__sub-item';
    }

    parent::display_element($element, $children_elements, $max_depth, $depth, $args, $output);
  }
  // @codingStandardsIgnoreEnd

  public function cssClasses($classes, $item) {
    $slug = sanitize_title($item->title);

    if ($this->cpt) {
      $classes = str_replace('current_page_parent', '', $classes);

      if (Utils\url_compare($this->archive, $item->url)) {
        $classes[] = 'active';
      }
    }

    $classes = preg_replace('/(current(-menu-|[-_]page[-_])(item|parent|ancestor))/', 'active', $classes);
    $classes = preg_replace('/^((menu|page)[-_\w+]+)+/', '', $classes);

    $classes[] = 'nav__item--' . $slug;

    $classes = array_unique($classes);

    return array_filter($classes, 'Roots\\Soil\\Utils\\is_element_empty');
  }
}

/**
 * Clean up wp_nav_menu_args
 *
 * Remove the container
 * Remove the id="" on nav menu items
 */
function nav_menu_args($args = '') {
  $nav_menu_args = [];
  $nav_menu_args['container'] = false;

  if (!$args['items_wrap']) {
    $nav_menu_args['items_wrap'] = '<ul class="%2$s">%3$s</ul>';
  }

  if (!$args['walker']) {
    $nav_menu_args['walker'] = new NavWalker();
  }

  return array_merge($args, $nav_menu_args);
}

In your header.php file reference the namespace and initiate the custom walker like so:

<?php

use Roots\Soil\CustomNav;

// Other header jazz

wp_nav_menu( array(
  'theme_location' => 'primary_navigation',
  'walker' => new NavWalker(),
) );

It’s important to note that when you declare use Roots\Soil\CustomNav; it needs to be in the same file as the call to new NavWalker()

Hmm I just tried all of that and still getting the same error! Fatal error: Class 'NavWalker' not found

I’m using Sage v 8.4.0.

It seems like anything I do in lib/*.php doesn’t get recognised in the front end.

I’ve still got add_theme_support('soil-nav-walker'); commented out.

Ah I fixed it! Turns out I needed to reference the class in the use statement in header.php

<?php use Roots\Soil\CustomNav\NavWalker; ?>

@craigpearson any idea why it works like this? Perhaps a different Roots or PHP version?

1 Like

Sorry Nathan, that’s my fault for not trying the code and doing it verbatim. Doh!

Namespaces still trip me up but I find this post is a real help, and he uses Game of Thrones throughout the example which is a bonus: http://daylerees.com/php-namespaces-explained/

The code I originally provided needed either the addition of the class on the use statement just like you’ve provided, or correctly referencing like so on the instantiation: new CustomNav/NavWalker();