wprig icon indicating copy to clipboard operation
wprig copied to clipboard

Extending Walker Class is currently a Night-mare

Open devsrealm opened this issue 5 years ago • 13 comments

Issue Overview

First off, I promised myself not to ask this very question anywhere, but honestly, this is beyond me (fed-up), I am trying to create a Mega site Navigation and after research upon researching, it seems the only way out is to extend the walker class, and re-engineer it to fit your desired output.

Describe your environment

Version: 1.37.1 (user setup) Date: 2019-08-15T16:17:55.855Z Electron: 4.2.7 Chrome: 69.0.3497.128 Node.js: 10.11.0 V8: 6.9.427.31-electron.0 OS: Windows_NT x64 10.0.10240

Steps to Reproduce

  1. Create a new Class under Nav_Menus -> Mega_Nav_Menu.php Since I will be creating a Mega Nav which opens and closes an element, I will be using the 'start_el' method and the 'end_el' method.

This is what I did:

<?php
/**
 * WP_Rig\WP_Rig\Nav_Menus\Mega_Nav_Menu class
 *
 * @package wp_rig
 */

namespace WP_Rig\WP_Rig\Nav_Menus;

use function WP_Rig\WP_Rig\wp_rig;
use WP_Post;
use function add_filter;
use function add_action;
use function get_theme_mod;

/**
 * Class for managing the mega navigation menu.
 */
class Mega_Nav_Menu {

	const SLUG = 'Mega';

	/**
	 * Adds the action and filter hooks to integrate with WordPress.
	 */
	public function initialize() {
		add_filter( 'walker_nav_menu_start_el', [ $this, 'add_extra_elements_nav' ], 10, 4 );
		add_filter( 'walker_nav_menu_end_el', [ $this, 'add_extra_elements_nav_end' ], 10, 4 );
	}

	/**
	 * Adds extra ul's to the parent ul e.g <ul><ul = "the extra ul"></ul></ul>
	 * This is the start_el of the walker class
	 *
	 * @param string  $item_output The menu item's starting HTML output.
	 * @param WP_Post $item        Menu item data object.
	 * @param int     $depth       Depth of menu item. Used for padding.
	 * @param object  $args        An object of wp_nav_menu() arguments.
	 * @return string Modified nav menu HTML.
	 */
	class my_walker extends Walker_Nav_Menu {
	public function add_extra_elements_nav( string $item_output, WP_Post $item, int $depth, $args ) : string {

		// Only for our primary menu location.
		if ( empty( $args->theme_location ) || static::SLUG !== $args->theme_location ) {
			return $item_output;
		}

		$indent  = ( $depth ) ? str_repeat( "\t", $depth ) : '';
		$classes = empty( $item->classes ) ? array() : (array) $item->classes;
		if ( in_array( 'new-column', $classes ) ) {
			$output .= '</ul><ul class="sub-menu">';
		}
		$classes[] = 'menu-item-' . $item->ID;
		$class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );
		$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
		$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 . '>';
		$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 : '';
		$atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth );
		$attributes = '';
		foreach ( $atts as $attr => $value ) {
			if ( ! empty( $value ) ) {
				$value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
				$attributes .= ' ' . $attr . '="' . $value . '"';
			}
		}
		$item_output = $args->before;
		$item_output .= '<a' . $attributes . '>';
		$item_output .= $args->link_before . $prepend . apply_filters( 'the_title', $item->title, $item->ID ) . $append;
		$item_output .= $description . $args->link_after;
		$item_output .= '</a>';
		$item_output .= $args->after;
		$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
		// Check to see if this is a parent list item, if it is open the mega-menu div
		if ( in_array( 'menu-item-has-children', $classes ) ) {
			$output .= '<div class="mega-menu">';
		}
	}


	/**
	 * Ends the waker class with necessary closings tags added to the start
	 *
	 * @param string  $item_output The menu item's starting HTML output.
	 * @param WP_Post $item        Menu item data object.
	 * @param int     $depth       Depth of menu item. Used for padding.
	 * @param object  $args        An object of wp_nav_menu() arguments.
	 * @return string Modified nav menu HTML.
	 */
	public function add_extra_elements_nav_end_el( string $item_output, WP_Post $item, int $depth, $args ) : string {

		// Check to see if this is a parent list item, if it is close the mega-menu div
		$classes = empty( $item->classes ) ? array() : (array) $item->classes;
		if ( in_array( 'menu-item-has-children', $classes ) ) {
			$output .= '</div><!-- END mega-menu -->';
		}

		$output .= "</li>\n";
	}

}
  1. Inside Nav_Menus -> Components.php I added this:
	/**
	 * Adds the action and filter hooks to integrate with WordPress.
	 */
	public function initialize() {
		$mega_nav_menu = new Mega_Nav_Menu();
		$mega_nav_menu->initialize();
	}

and this

	public function template_tags() : array {
		return [
			'is_mega_nav_menu_active'    => [ $this, 'is_mega_nav_menu_active' ],
			'display_mega_nav_menu'      => [ $this, 'display_mega_nav_menu' ],
		];
	}

Registered the Mega Nav Menu

	/**
	 * Registers the navigation menus.
	 */
	public function action_register_nav_menus() {
		register_nav_menus(
			[
				static::PRIMARY_NAV_MENU_SLUG => esc_html_x( 'Primary', 'nav menu', 'wp-rig' ),
				Mega_Nav_Menu::SLUG           => esc_html_x( 'Mega', 'nav menu', 'wp-rig' ),
			]
		);
	}

checked whether Mega menu is active

	public function is_mega_nav_menu_active() : bool {
		return (bool) has_nav_menu( Mega_Nav_Menu::SLUG );
	}

display mega menu

	public function display_mega_nav_menu( array $args = [] ) {
		if ( ! isset( $args['container'] ) ) {
			$args['container'] = false;
		}

		$args['theme_location'] = Mega_Nav_Menu::SLUG;

		wp_nav_menu( $args );
	}
  1. Then, I went into the template I want the mega menu to display, the previous one was the primary menu, just replaced with the mega menu, the below is what I did
<?php
/**
 * Template part for displaying the complete unique header
 *
 * @package wp_rig
 */

namespace WP_Rig\WP_Rig;

if ( ! wp_rig()->is_mega_nav_menu_active() ) {
	return;
}
?>

<div class="site-logo">
	<?php the_custom_logo(); ?>
</div><!-- .site-logo -->

<nav id="site-navigation" class="collapse main-navigation nav--toggle-sub nav--toggle-small" aria-label="<?php esc_attr_e( 'Main menu', 'wp-rig' ); ?>"
	<?php
	if ( wp_rig()->is_amp() ) {
		?>
		[class]=" siteNavigationMenu.expanded ? 'main-navigation nav--toggle-sub nav--toggle-small nav--toggled-on' : 'main-navigation nav--toggle-sub nav--toggle-small' "
		<?php
	}
	?>
>
	<?php
	if ( wp_rig()->is_amp() ) {
		?>
		<amp-state id="siteNavigationMenu">
			<script type="application/json">
				{
					"expanded": false
				}
			</script>
		</amp-state>
		<?php
	}
	?>

	<button class="menu-toggle" aria-label="<?php esc_attr_e( 'Open menu', 'wp-rig' ); ?>" aria-controls="primary-menu" aria-expanded="false"
		<?php
		if ( wp_rig()->is_amp() ) {
			?>
			on="tap:AMP.setState( { siteNavigationMenu: { expanded: ! siteNavigationMenu.expanded } } )"
			[aria-expanded]="siteNavigationMenu.expanded ? 'true' : 'false'"
			<?php
		}
		?>
	>
		<?php esc_html_e( 'Menu', 'wp-rig' ); ?>
	</button>

	<div class="mega-menu-container">
		<?php wp_rig()->display_mega_nav_menu( [ 'menu_id' => 'primary-menu' ] ); ?>
</nav><!-- #site-navigation -->

Expected Behavior

I expected the extended walker class or menu to be created

Current Behavior

I had an unexpected behaviour, see image https://prnt.sc/ovldzr

Possible Solution

Normally, to create a walker class of the kind I want,

There should be a class like this:

class my_walker extends Walker_Nav_Menu {    
public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {        // Code for start_el goes here    }    
public function end_el( &$output, $item, $depth = 0, $args = array() ) {        // Code for end_el goes here    }}
--

Give us an option to add a new class to the nav_menu

Screenshots / Video

Related Issues and/or PRs

devsrealm avatar Aug 21 '19 12:08 devsrealm

This is a @felixarntz question

mor10 avatar Aug 22 '19 16:08 mor10

Related to #560

mor10 avatar Aug 22 '19 17:08 mor10

Wprig is currently the best starter theme I have ever tried, so I resorted to using a plugin to handle the Mega Navigation.

devsrealm avatar Aug 23 '19 07:08 devsrealm

@Horlaes , just wondering a few things about your code sample.

  1. noticed youre started with class Mega_Nav_Menu and are using initialize(), but didnt include implements Component_Interface. Which if I understand correctly (I might not) means the theme doesnt know what to do with initialize().

  2. Similarly, did you try class Mega_Nav_Menu extends \Walker_Nav_Menu implements Component_Interface instead of resorting to a subclass? As I wrote in the related issue that was posted above, that declaration worked fine for me with \WP_Widget.

justlevine avatar Aug 26 '19 23:08 justlevine

1.) I don't need to implement the component interface anymore, as that has been used in the main Nav component and If you carefully read what I posted above, you'll notice I created the Mega Nav file under a parent folder (Nav_Menu in this case, and it has the component loaded already), then inside the main Component (Component.php), I added the call to it.

2.) Widget is a different case, I need a way to walk through the menu items, I already achieved this with my previous themes, I think it is not just supported yet or perhaps it is, which I don't know how to achieve, I have ruled that off my list currently (spent close to 2 weeks before asking).

Thanks for the heads up man, appreciate!

devsrealm avatar Aug 27 '19 07:08 devsrealm

Hello @Horlaes you manage to solve this detail, could you please share code to see how you solved?. Thanks for your help

dackr avatar Jan 26 '20 20:01 dackr

or someone please managed to do this from the menu.

dackr avatar Jan 26 '20 20:01 dackr

@mor10 Hello please can you tell me how to make or add the wallker to the menu please?.

dackr avatar Jan 27 '20 21:01 dackr

@dackr the walker class is currently not supported in wp-rig, you can't extend the walker-class, the only thing you can do is to create a plugin or use download one from the repo. This stack overflow answer should also help: https://wordpress.stackexchange.com/questions/116708/customizing-walker-nav-menu

devsrealm avatar Jan 28 '20 13:01 devsrealm

@Horlaes Thanks for your help.

I want this topic to be important for a wprig improvement

dackr avatar Jan 30 '20 18:01 dackr

Any update about this?? It's not the walker clases supported yet? What's the status of this improvement?? Thank you for the good work done in wprig @mor10 @Horlaes

Orlegi avatar Apr 12 '20 21:04 Orlegi

If I try and require a file, say my-custom-walker-class.php, and then call the walker from header.php, I get an error similar to:

: Uncaught Error: Class 'WP_Rig\WP_Rig\Custom_Walker_Nav_Main_Menu' not found in /www/kinsta/public/devmysite/wp-content/themes/wprig/header.php:112 Stack trace: #0 /www/kinsta/public/devmysite/wp-includes/template.php(730): require_once() #1 /www/kinsta/public/devmysite/wp-includes/template.php(676): load_template() #2 /www/kinsta/public/devmysite/wp-includes/general-template.php(48): locate_template() #3 /www/kinsta/public/devmysite/wp-content/themes/wprig/home-page-template.php(17): get_header() #4 /www/kinsta/public/devmysite/wp-includes/template-loader.php(106): include('/www/kinsta/pub...') #5 /www/kinsta/public/devmysite/wp-blog-header.php(19): require_once('/www/kinsta/pub...') #6 /www/kinsta/public/devmysite/index.php(17): require('/www/kinsta/pub...') #7 {main} thrown in

What exactly is going on here? Why can't I require a class directly? Where is load_template coming into play?

ChrisBuck-Harness avatar Feb 10 '21 23:02 ChrisBuck-Harness

@ChrisBuck-Harness how are you including Custom_Walker_Nav_Main_Menu? WP_Rig doesn't have real autoloading (it only autoloads the various new Component() statements....

justlevine avatar Feb 10 '21 23:02 justlevine