Skip to content

Instantly share code, notes, and snippets.

@smutek
Last active October 4, 2024 21:58
Show Gist options
  • Save smutek/4e484d4aebe209539d4c72dc1f332bba to your computer and use it in GitHub Desktop.
Save smutek/4e484d4aebe209539d4c72dc1f332bba to your computer and use it in GitHub Desktop.
Bootstrap 5 Nav Walker for Sage 10

Bootstrap 5 Walker for Sage 10

Forked from the clean NavWalker from the Roots/Soil Plugin which was deprecated in favor of acorn-prettify via composer. I'm using acorn-prettify in my project and as far as I can tell it does not include the clean NavWalker markup from Soil. This fork maintains the features of Soil's clean NavWalker and adds required Bootstrap 5 css classes to the Walker_Nav_Menu in a Sage 10 project.

I'm using this currently in a project along with acorn-prettify and everything appears to be working fine. Does not require acorn-prettify to work. Use at your own risk.

Assumptions

  • Assumes you're using Sage 10.
  • Assumes Tailwind has been removed from the Sage project and Bootstrap 5 has been added.

To use

  • Add BootstrapNav.php to the themes /app directory.
  • Replace the contents of /resources/views/sections/header.blade.php with the version in this gist.

Limitations

  • Only supports 1 level of dropdowns.
  • There are probably better versions of this out there.

Resources

<?php
namespace App;
use Walker_Nav_Menu;
use function get_post_type;
use function get_post_types;
use function get_post_type_archive_link;
use function is_search;
use function sanitize_title;
use function add_filter;
use function remove_filter;
/**
* This NavWalker was forked from Roots/Soil/src/NavWalker.
* The Soil plugin has been deprecated in favor of Acorn/Acorn-Prettify and is no longer maintained.
* This NavWalker includes Soil's cleaner navigation markup and is modified to work with Bootstrap 5.
* @link https://github.com/roots/soil/
* @link https://getbootstrap.com/docs/5.0/components/navbar/
* 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 menu-item nav-item active"><a href="/" class="nav-link">Home</a></li>
* <li class="menu-sample-page menu-item nav-item"><a href="/sample-page/" class="nav-link">Sample Page</a></li>
*
*/
class BootstrapNav extends Walker_Nav_Menu
{
/**
* Is current post a custom post type?
*
* @var bool
*/
protected $is_cpt;
/**
* Archive page for current URL.
*
* @var string
*/
protected $archive;
public function __construct()
{
$cpt = get_post_type();
$this->is_cpt = in_array($cpt, get_post_types(array('_builtin' => false)), true);
$this->archive = get_post_type_archive_link($cpt);
$this->is_search = is_search();
}
public function checkCurrent($classes)
{
return preg_match('/(current[-_])|active/', $classes);
}
public function displayElement($element, &$children_elements, $max_depth, $depth, $args, &$output)
{
$element->is_subitem = ((!empty($children_elements[$element->ID]) && (($depth + 1) < $max_depth || ($max_depth === 0))));
$element->is_active = (!empty($element->url) && strpos($this->archive, $element->url));
if ($element->is_active && !$this->is_search) {
$element->classes[] = 'active';
}
parent::display_element($element, $children_elements, $max_depth, $depth, $args, $output);
}
public function cssClasses($classes, $item)
{
$slug = sanitize_title($item->title);
// Fix core `active` behavior for custom post types
if ($this->is_cpt) {
$classes = str_replace('current_page_parent', '', $classes);
if ($this->archive && !$this->is_search) {
if (strpos($item->url, $this->archive) !== false) {
$classes[] = 'active';
}
}
}
// Remove most core classes
$classes = preg_replace('/(current(-menu-|[-_]page[-_])(item|parent|ancestor))/', 'active', $classes);
$classes = preg_replace('/^((menu|page)[-_\w+]+)+/', '', $classes);
// Re-add core `menu-item` class
$classes[] = 'menu-item';
// Add Bootstrap classes
if ($item->menu_item_parent == 0) {
$classes[] = 'nav-item';
}
if ($item->is_subitem) {
$classes[] = 'dropdown';
}
// Re-add core `menu-item-has-children` class on parent elements
if ($item->is_subitem) {
$classes[] = 'menu-item-has-children';
}
// Add `menu-<slug>` class
$classes[] = 'menu-' . $slug;
$classes = array_unique($classes);
$classes = array_map('trim', $classes);
return array_filter($classes);
}
/**
* Add "dropdown-menu" class to dropdown UL
* @param $classes
* @return array
*/
function dropdownListClass($classes): array
{
$classes[] = 'dropdown-menu';
return $classes;
}
/**
* Add Bootstrap 5 classes and attributes to anchor links.
* This method originally created by QWp6t.
* @param $atts
* @param $item
* @param $args
* @param $depth
* @return array
* @SuppressWarnings(PHPMD.UnusedFormalParameter) This method overrides its parent
* @link (credit) https://gist.github.com/QWp6t/8f94b7096bb0d3a72fedba68f73033a5#file-bootstrap-php-L63
*/
public function linkAttributes($atts, $item, /** @noinspection PhpUnusedParameterInspection */ $args, $depth): array
{
$atts['class'] = $depth ? 'dropdown-item' : 'nav-link';
if ($item->current || $item->current_item_ancestor) {
$atts['class'] .= ' active';
}
if ($item->is_subitem) {
$atts['class'] .= ' dropdown-toggle';
$atts += [
'data-bs-toggle' => 'dropdown',
'role' => 'button',
'aria-expanded' => 'false'
];
}
return $atts;
}
public function walk($elements, $max_depth, ...$args)
{
// Add filters
add_filter('nav_menu_css_class', array($this, 'cssClasses'), 10, 2);
add_filter('nav_menu_item_id', '__return_null');
add_filter('nav_menu_submenu_css_class', [$this, 'dropdownListClass'], 10, 2);
add_filter('nav_menu_link_attributes', [$this, 'linkAttributes'], 10, 4);
// Perform usual walk
$output = call_user_func_array(['parent', 'walk'], func_get_args());
// Unregister filters
remove_filter('nav_menu_css_class', [$this, 'cssClasses']);
remove_filter('nav_menu_item_id', '__return_null');
remove_filter('nav_menu_submenu_css_class', [$this, 'dropdownListClass']);
remove_filter('nav_menu_link_attributes', [$this, 'linkAttributes']);
// Return result
return $output;
}
/**
* Everything below this line is passthrus for WordPress.
* phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
*/
public function display_element($element, &$children_elements, $max_depth, $depth, $args, &$output)
{
return $this->displayElement($element, $children_elements, $max_depth, $depth, $args, $output);
}
}
<header class="banner">
@if (has_nav_menu('primary_navigation'))
<nav class="navbar navbar-expand-lg navbar-light bg-light"
aria-label="{{ wp_get_nav_menu_name('primary_navigation') }}">
<div class="container">
<a class="navbar-brand" href="{{ home_url('/') }}">
{!! $siteName !!}
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#main-nav"
aria-controls="main-nav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="main-nav">
{!! wp_nav_menu([
'theme_location' => 'primary_navigation',
'menu_class' => 'navbar-nav me-auto mb-2 mb-lg-0',
'walker' => new \App\BootstrapNav(),
]) !!}
</div>
</div>
</nav>
@endif
</header>
@smutek
Copy link
Author

smutek commented Sep 18, 2023

Awesome, glad it was helpful! :)

@mheiduk
Copy link

mheiduk commented Nov 22, 2023

Thank you very much, works like a charm!

@Schwankenson
Copy link

Thank you, that`s great!

  • I had to add 'container' => false to wp_nav_menu call to remove the div and be able to position a search field right of the menu items
  • I had to add bootstrapNav in functions.phpto load the script with the nav walker

@smutek
Copy link
Author

smutek commented Dec 23, 2023

@Schwankenson thanks for the info and glad it worked out!

If you want to remove the walker from your functions.php try naming the file BootstrapNav.php, with the capitalized B, it will then be recognized as a PHP class and will be automagically picked up by the class autoloader.

@Schwankenson
Copy link

@Schwankenson thanks for the info and glad it worked out!

If you want to remove the walker from your functions.php try naming the file BootstrapNav.php, with the capitalized B, it will then be recognized as a PHP class and will be automagically picked up by the class autoloader.

Great, thank you. Did not know about this autoloading thing.

@smutek
Copy link
Author

smutek commented Oct 4, 2024

I removed the dependency for Soil since the Soil plugin is no longer under development or receiving support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment