Right Way to Add New Walker to Sage 9

Even better would be to use psr-4 (or psr-0) and add the namespace and path to composer.json.

The only reason we manually load files like that in functions.php is because everything in sage/app (formerly sage/src) is procedural code that can’t make use of spl autoloader. Everything in lib is classes, which are autoloaded by composer. So just add your own namespace (I use use App\Theme or something similar) and whip up dem classes ezpz

I have a Foundation Sites NavWalker that extends the one used in Soil. I can share that later when I get home if anyone is interested.

3 Likes

Great, this worked like a charm. Thank you very much @JulienMelissas. The App namespace booted an error, but I removed it and everything works great.

On a side note, for anyone interested:

I got this working without any walker at all before reading @JulienMelissas’s answer, the following way (but his way is much better, use that one):

@php
    mainNavArgs = [
        'container' => 'ul',
        'menu' => __('Primary Navigation', 'sage'),
        'menu_class' => 'vertical menu',
        'theme_location' => 'primary_navigation',
        'items_wrap' => '<ul id="%1$s" class="%2$s" data-drilldown="" data-close-on-click>%3$s</ul>'
   ]
@endphp

{!! wp_nav_menu($mainNavArgs) !!}

The menu will still be rendered as a Drilldown, and in fact will add less classes to the elements than the other F6 Walker.

There are some caveats and you will probably need a little jQuery to add the vertical menu classes to the submenus (elements with children):

$('.is-drilldown-submenu').addClass('vertical menu'); on common.js should do the trick.

You’ll also need some CSS to make up for the fact that elements won’t have the exact same classes. I just added:

.is-drilldown-submenu {
  margin: 0;

  &.is-active {
    z-index: 0;
  }
}

…and everything ran smoothly.

Again, you’re probably better off following @JulienMelissas’s instructions and using the Walker which obviously gives you more control over the content, but if you’re looking for a quick, no-frills, beginner-friendly way to do it, this should help.

That would be great! I just created a repo on Github using the Walker I’d found but yours is probably better since it implements Soil’s walker. Please feel free to fork/PR/whatever.

I’m interested in seeing your implementation if you don’t mind.

I don’t know how my NavWalkers stack up against what’s already out there. I wrote these for my specific needs in two different projects, and they worked for me. Neither were intended for wider distribution, but here you go anyway. One is for Bootstrap and the other is for Foundation.

4 Likes

Maybe it will help someone.
I have situation like this in sage9-beta3.

I created new class file in new folder called Classes inside sage/app/lib/Sage/. I’ve created MainWalker.php file that looks like this:

MainWalker.php

namespace Roots\Sage\Classes;

use Roots\Soil\Nav\NavWalker as NavWalker;

/*
 * @author brunekninja
 */
class MainWalker extends NavWalker 
{
}

After that I just use that class as walker in my new nav menu inside header.blade.php

header.blade.php

<header class="page-header" data-method="stickyHeader">
  <div class="container">
    <nav class="site-navigation">

      @if (has_nav_menu('primary_navigation'))
        {!! wp_nav_menu([
        'theme_location' => 'primary_navigation',
        'menu_class' => 'site-navigation__main-nav',
        'container_class' => 'site-navigation__main-nav',
        'walker' => new Roots\Sage\Classes\MainWalker()
        ]) !!}
      @endif

That’s it, now you can create your custom nav walker in sage 9.
Hope it helps someone.

1 Like

Hi bruno. I tried this approach as it seemed very clean but I’m getting Fatal error: Class ‘Roots\Soil\Nav\NavWalker’ not found.
Used your code verbatim but am on Sage 9 beta 4 so i created the MainWalker class under /app and included it in functions.php as a required Sage include. Seen a lot of talk of namespace issues :confused:

1 Like

That method requires having Soil installed and activated

yep. i have installed and activated. Can see the clean soil nav structure.

Update, i resolved this with the help of smutek’s gist… FTW!

1 Like

Hey,

yes beta 4 has different structure, and also it is true that this approach requires Soil to be activated. But eventually you can just extend main navwalker class from WP and it has to work.

I checked the new structure, and there is small difference. I just putted navigation folder inside app.

48

Only thing that has to be changed is namespace, and call of nav walker.

So you have situation like this.

namespace App\Navigation;

this is namespace inside MainWalker file.

@if (has_nav_menu('primary_navigation'))
    {!! wp_nav_menu([
     'theme_location' => 'primary_navigation',
     'menu_class' => 'site-navigation__main-nav',
     'walker' => new \App\Navigation\MainWalker()
    ]) !!}     
@endif

And it works!

3 Likes

I followed the instructions of @brunekninja, but have the same problem as @thewhipster: Fatal error: Class ‘Roots\Soil\Nav\NavWalker’ not found.

I’m using Trellis/Bedrock/Sage 9 beta 4, with Soil installed and activated.

The same question was asked here, but never resolved.

Any other thoughts on why the class can’t be found, even though Soil is installed and activated?

1 Like

Hi, I had that problem on linux server in production, before that everything worked fine in docker etc.
Eventually I’ve added this in composer.json file.

"autoload": {
    "psr-4": {
     "App\\": "web/app/themes/sage/app/",
     "App\\Widgets\\": "web/app/themes/sage/app/widgets/"
    }
 }

After I registered classes this way, error have disappeared

You can try this, this is the only solution that I can think of.

1 Like

I tried brunekninja’s suggestion and several other autoload options in composer.json files for Bedrock, Sage, and Soil without success. I finally got this to work by loading the Foundation walker file on its own after Soil modules are loaded (and not including the Foundation walker in the Sage required files array). At the bottom of soil.php the modules are loaded with after_setup_theme, priority 100. So my Sage functions.php file now has this:

/**
 * Sage required files
 *
 * The mapped array determines the code library included in your theme.
 * Add or remove files to the array as needed. Supports child theme overrides.
 */
array_map(function ($file) use ($sage_error) {
    $file = "../app/{$file}.php";
    if (!locate_template($file, true, true)) {
        $sage_error(sprintf(__('Error locating <code>%s</code> for inclusion.', 'sage'), $file), 'File not found');
    }
}, ['helpers', 'setup', 'filters', 'admin']);

/**
 * Foundation Nav Walker
 *
 * Load the Foundation nav walker after Soil plugin modules are loaded so that Soil's NavWalker class is available.
 */
add_action( 'after_setup_theme', 'add_foundation_nav_walker', 101 );
function add_foundation_nav_walker() {

    if ( class_exists('\Roots\Soil\Nav\NavWalker') ) {

        require get_template_directory() . '/../app/nav-foundation-walker.php';
    }
}

I’m new to psr-4 and namespaces in php, but I gather that the reason for autoloading with psr-4 is to allow classes to be available independently of how they might otherwise be loaded via require, etc. Soil’s compser.json file currently doesn’t autoload anything. Should it include the following?

"autoload": {
    "psr-4": { "Roots\\Soil\\Nav": "modules" }
 },

When I try that and run composer update in Soil, the generated autoload file at soil/vendor/composer/autoload_psr4.php file correctly shows:

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Roots\\Soil\\Nav\\' => array($baseDir . '/modules'),
    'Composer\\Installers\\' => array($vendorDir . '/composer/installers/src/Composer/Installers'),
);

But if I then load nav_foundation_walker.php via the Sage required array I still get “Fatal error: Class ‘Roots\Soil\Nav\NavWalker’ not found” when loading the site in my browser. I assume the correct way to do this is with autoloading, but I’m not sure how to make that work.

1 Like

I’m having a somewhat similar problem. I have my Walkers working on development with no problems, but when I upload the theme to the prod server, I get the same “Walker not found error”. Weird.

1 Like

You need to run composer install in the theme directory when you deploy a Sage 9 theme:

That was in reply to Right Way to Add New Walker to Sage 9, right? Running composer install in the theme doesn’t solve my problem.

Hard to help without anything to go off.

Please provide the exact code that you are using/changes that you have made to Sage.

Hi Ben! Thanks - will try.

I’m running Windows 10 with Trellis/Bedrock/Sage 9-beta.4, and Soil installed via composer and activated.

In sage/app/ I’ve added a file nav-foundation-walker.php with QWp6t’s Foundation.php walker. So the file looks like this:

<?php 
namespace App;

use Roots\Soil\Nav\NavWalker as SoilNavWalker;

/**
 * Foundation 6 Navigation Walker
 *
 * @author QWp6t
 * @license OSL-3.0
 * @see https://gist.github.com/QWp6t/8f94b7096bb0d3a72fedba68f73033a5
 */

class FoundationWalker extends SoilNavWalker
{
    public function __construct()
    {
        parent::__construct();
        remove_filter('nav_menu_css_class', [$this, 'cssClasses'], 10);
        add_filter('nav_menu_css_class', [$this, 'itemClasses'], 10, 4);
    }
    /**
     * @param string $output
     * @param int $depth
     * @param array $args
     * @SuppressWarnings(PHPMD.CamelCaseMethodName) This method overrides its parent
     * @SuppressWarnings(PHPMD.UnusedFormalParameter) This method overrides its parent
     */
    // @codingStandardsIgnoreLine
    public function start_lvl(&$output, $depth = 0, $args = [])
    {
        $output .= '<ul class="submenu menu" data-submenu>';
    }
    /**
     * @param $classes
     * @param $item
     * @param $args
     * @param $depth
     * @return array
     * @SuppressWarnings(PHPMD.UnusedFormalParameter) This method overrides its parent
     */
    public function itemClasses($classes, $item, /** @noinspection PhpUnusedParameterInspection */ $args, $depth)
    {
        return array_filter(array_map(function ($class) use ($depth) {
            switch ($class) {
                case 'menu-item-has-children':
                    return 'has-submenu';
                default:
                    return $class;
            }
        }, parent::cssClasses($classes, $item)));
    }
}

Then in sage/resources/functions.php I added nav-foundation-walker to the Sage required files:

/**
 * Sage required files
 *
 * The mapped array determines the code library included in your theme.
 * Add or remove files to the array as needed. Supports child theme overrides.
 */
array_map(function ($file) use ($sage_error) {
    $file = "../app/{$file}.php";
    if (!locate_template($file, true, true)) {
        $sage_error(sprintf(__('Error locating <code>%s</code> for inclusion.', 'sage'), $file), 'File not found');
    }
}, ['helpers', 'setup', 'filters', 'admin', 'nav-foundation-walker']);

When I load the site in a browser I get the error:

Fatal error: Class 'Roots\Soil\Nav\NavWalker' not found in /srv/www/test.com/current/web/app/themes/sage/app/nav-foundation-walker.php

How do I use Composer and autoloading so that Roots\Soil\Nav\Navwalker is available in nav-foundation.php?

I’ve tried modifying Soil’s composer.json file by adding:

"autoload": {
    "psr-4": {
      "Roots\\Soil\\Nav\\": "modules/"
    }
  },

And I’ve tried modifying Sage’s composer.json file by adding:

"autoload": {
    "psr-4": {
      "App\\": "app/",
      "Roots\\Soil\\Nav\\": "../../plugins/Soil/modules/"
    }
  },

Each time I ran composer update to create a new vendor/autoload_psr4.php file. I also tried something similar in Bedrock’s composer.json file. None of these worked. What am missing here?

1 Like