WordPress / gutenberg

The Block Editor project for WordPress and beyond. Plugin is available from the official repository.
https://wordpress.org/gutenberg/
Other
10.52k stars 4.21k forks source link

[Navigation block] Submenu items in mobile menu collapsed by default #38599

Open QROkes opened 2 years ago

QROkes commented 2 years ago

What problem does this address?

Submenus are all expanded when mobile menu is open, which can cause a mess when you have several submenu items.

What is your proposed solution?

An option to select the default submenu behavior in mobile menu.

MvS-IT commented 2 years ago

Hello, You mention "the default submenu behavior". I'm not exactly sure if my solution below covers you're issue, but I give it a go ;-):

What I noticed was that on a white background I did not see the submenu button which has a svg image for a down-arrow, the socalled chevron. With some css styling I changed .wp-block-navigationsubmenu-icon { display: none; } to: .wp-block-navigationsubmenu-icon { display: inline-block !important; }

I used the following javascript code to open and close submenus:

var navMenu = { ready: (callback) => { // In case the document is already rendered if (document.readyState!='loading') callback(); else document.addEventListener('DOMContentLoaded', callback); },

subMenus: document.querySelectorAll('.wp-block-navigation .wp-block-navigation-submenu__toggle'),

toggleSubmenuOnClick: (event) => {

    const buttonToggle = event.target.closest('[aria-expanded]');

    const subMenu = buttonToggle.nextSibling;

    const isSubmenuOpen = subMenu.style.getPropertyValue("opacity");

    if (isSubmenuOpen == 1) {
        subMenu.style.setProperty("opacity", "0", "important");
        subMenu.style.setProperty("height", "0", "important");
        subMenu.style.setProperty( "padding-top", "0px",  "important");
        subMenu.style.setProperty( "padding-bottom", "0px",  "important");
    } else {
        subMenu.style.setProperty("opacity", "1", "important");
        subMenu.style.setProperty("height", "auto", "important");
        subMenu.style.setProperty( "padding-top", "20px", "important"); 
        subMenu.style.setProperty( "padding-bottom", "20px",  "important");
    }
  }

}

navMenu.ready(() => { navMenu.subMenus.forEach((subMenu)=>{ subMenu.addEventListener('click', (event)=>{ navMenu.toggleSubmenuOnClick(event); }); }); });

It is enqueued with php: function my_custom_load_scripts() {

// create own version codes
$my_js_ver  = date("ymd-Gis", filemtime( plugin_dir_path( __FILE__ ) . '/assets/js/my-custom-navigation.js' ));
wp_enqueue_script( 'my_custom_navigation', plugins_url( '/assets/js/my-custom-navigation.js', __FILE__ ), array(), $my_js_ver, true );

} add_action('wp_enqueue_scripts', 'my_custom_load_scripts');

I hope this might help.

MvS-IT commented 2 years ago

One thing to add: on mobile devices the submenu's won't open because of z-index: 2; change this to: .wp-block-navigation__submenu-container{ z-index: 0 !important; } and it'll work

MvS-IT commented 2 years ago

Since WP 6.0 this does not work anymore

MvS-IT commented 2 years ago

In WP 6.0 the following JS code works:

var navMenu = { ready: (callback) => { // In case the document is already rendered if (document.readyState!='loading') callback(); else document.addEventListener('DOMContentLoaded', callback); },

subMenuLinks: '',
subMenuContainers: '',

writeToLog: (message) =>{
    console.log( message )
},

toggleSubmenuOnClick: (aSubMenuContainer) => {

    const isSubmenuOpen = aSubMenuContainer.style.getPropertyValue("opacity");

    navMenu.writeToLog('isSubmenuOpen: ' + isSubmenuOpen);

    if (isSubmenuOpen == 1) {
        aSubMenuContainer.style.setProperty("opacity", "0", "important");
        aSubMenuContainer.style.setProperty("height", "0", "important");
        aSubMenuContainer.style.setProperty( "padding-top", "0px",  "important");
        aSubMenuContainer.style.setProperty( "padding-bottom", "0px",  "important");
    } else {
        aSubMenuContainer.style.setProperty("opacity", "1", "important");
        aSubMenuContainer.style.setProperty("height", "auto", "important");
        aSubMenuContainer.style.setProperty( "padding-top", "20px", "important"); 
        aSubMenuContainer.style.setProperty( "padding-bottom", "20px",  "important");
    }
  }

}

navMenu.ready(() => {

navMenu.subMenuLinks = document.querySelectorAll('.wp-block-navigation-submenu');
navMenu.subMenuContainers = document.querySelectorAll('.wp-block-navigation__submenu-container');
navMenu.subMenuLinks.forEach((subMenuLink, idx)=>{

    subMenuLink.addEventListener('click', ()=>{
        navMenu.toggleSubmenuOnClick(navMenu.subMenuContainers[idx]);
    });
});

});

dennisheiden commented 2 years ago

Looks like that behavior was necessary due the lack of mobile block settings on implementation. The block works fine with just top level menu items but looks very unfinished and outdated on mobile mode.

masteradhoc commented 1 year ago

+1

antonyjsmith commented 1 year ago

+1 from me and a related issue is here #44346

waqastariqkhan commented 1 year ago

+1 is there any update on this yet?

mgozdis commented 1 year ago

+1 Feature requested by user.

DarkHouseDevelopment commented 1 year ago

+1 If anything, having the submenus collapsed and expandable by default would make far more sense, as this is the norm and best UX for mobile menus with multiple levels.

veerap000 commented 1 year ago

+1 collapsing submenus for mobile is must have functionality basically for any site that has more that 10 items in navigation. JS or CSS workaround is not desired option since the source (structure/class names) of navigation block seems not to be stable.

mgozdis commented 1 year ago

Another +1 from a user.

jordesign commented 1 year ago

Another +1 in the forums: https://wordpress.org/support/topic/submenu-should-be-collapsed/?view=all

bradhogan commented 10 months ago

+1 really lacking here compared with mobile menu options on other platforms (sqs, webflow, wix, etc.). A big downside to any sites with a larger amount of content that can't sacrifice having sub-menus excluded from their header menu on mobile.

pareshsojitra commented 9 months ago

I just fixed this doing some tweak. if anyone looking for this. without blocking main menu link.

I can also share if anyone still looking to this.

I have done this in latest WordPress Theme.

image

CSS: @media (max-width:599px) { header .wp-block-navigation .wp-block-navigation-item { justify-content: center; } header .wp-block-navigation ul li { width: 100%; } header .wp-block-navigation .wp-block-navigation__submenu-icon { height: auto; width: auto; position: absolute; border: 1px solid #ccc; padding: 3px; right: 0; top: 4px; } header .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .has-child .wp-block-navigation__submenu-container { display: none; width: 100%; } header .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .wp-block-navigation__submenu-icon { display: block; } }

jQuery: /* + Document On Ready */ $(document).on("ready", function() { $(".wp-block-navigation-submenu__toggle").on("click", function() { var li = $(this).parent(); if ( li.hasClass("ddl-active") || li.find(".ddl-active").length !== 0 || li.find(".wp-block-navigation-submenu").is(":visible") ) { li.removeClass("ddl-active"); li.children().find(".ddl-active").removeClass("ddl-active"); li.children(".wp-block-navigation-submenu").slideUp(); } else { li.addClass("ddl-active"); li.children(".wp-block-navigation-submenu").slideDown(); } }); });

pareshsojitra commented 9 months ago

Here is how it works without any issue. I hop this considered to be added in WordPress Core ? Anyone can do this how.

https://github.com/WordPress/gutenberg/assets/5764334/4dfb6f3c-257b-4a6f-99b6-fb2f115e4e34

jaxtheking commented 7 months ago

+1 from me too. Menus are a complex aspect (and often individual) of a website so we need flexibility. While the Navigation Block grew on me, I think it's still a little too minimalistic to cover enough cases.

DecimusBurton commented 6 months ago

@pareshsojitra unable to get the js part to wrotk which I added as a snippet

` function wpb_hook_javascript_footer() { ?>

<?php

} add_action('wp_footer', 'wpb_hook_javascript_footer'); `

pareshsojitra commented 5 months ago

Use code in between the following.

(function($) {

    "use strict";

})(jQuery);
MvS-IT commented 2 months ago

Good evening, it's been a while since I posted a potential solution without the use of jQuery. Thanks for the thumbs. Now that we have the wp-interactivity api, it might be tempting to use this new api. So I asked ChatGPT for some help with this. It came up with the following suggestions:

Step 1: Create a plugin folder

Create a new folder in the wp-content/plugins folder, for example custom-interactivity-menu. In this folder, create a PHP file, for example custom-interactivity-menu.php Step 2: Basic Plugin Code: `<?php / Plugin Name: Custom Interactivity Menu Description: A plugin to add support multilevel menus in the navigation menu Version: 1.0 Author: Trimae.nl /

// Voorkom directe toegang tot het bestand if ( !defined( 'ABSPATH' ) ) exit;

// Register the custom menu walker function register_custom_interactivity_menu_walker() { require_once plugin_dir_path( FILE ) . 'includes/custom-interactivity-walker-nav-menu.php'; } add_action( 'init', 'register_custom_interactivity_menu_walker' );

// Functie om JavaScript-bestanden in te laden function custom_interactivity_menu_enqueue_scripts() {

if ( ! wp_script_is( 'wp-hooks', 'registered' ) ) {
    wp_register_script( 'wp-hooks', includes_url( '/js/dist/hooks.js' ), array(), null, true );
}

// Laad wp-hooks script en je custom script
wp_enqueue_script( 'wp-hooks' );

// Verbind het css bestand
wp_enqueue_style(
    'custom-interactivity-menu', // Naam van de stijl
    plugin_dir_url( __FILE__ ) . 'css/custom-interactivity-menu.css', // Pad naar het CSS-bestand
    array(), // Afhankelijkheid van andere stijlen
    '1.0.2', // Versie (je kunt ook een nummer plaatsen)
);

// Verbind het JavaScript bestand
wp_enqueue_script(
    'custom-interactivity-menu', // Naam van het script
    plugin_dir_url( __FILE__ ) . 'js/custom-interactivity-menu.js', // Pad naar het JS-bestand
    array( 'wp-hooks' ), // Afhankelijkheid van WP hooks (nodig voor WP Interactivity API)
    null, // Versie (je kunt ook een nummer plaatsen)
    true  // In de footer plaatsen
);

}

// Voeg de actie toe om de scripts te laden op de frontend add_action( 'wp_enqueue_scripts', 'custom_interactivity_menu_enqueue_scripts' );

// Voeg een shortcode toe om het menu weer te geven function custom_interactivity_menu() {

wp_nav_menu( array(
    'theme_location' => 'primary',  // Locatie van het menu
    'menu_class'     => 'main-menu',     // Voeg een aangepaste CSS-klasse toe
    'container'      => 'nav',           // Container voor het menu
    'walker'         => new Custom_Interactivity_Walker_Nav_Menu() // Gebruik de custom walker
) );

} add_shortcode( 'interactivity_menu', 'custom_interactivity_menu' );

` Step 3: Add JavaScript for use of the Interactivity API, add a js file in the plugin dir js called custom-interactivity-menu.js with the following content:

` console.log('Custom Interactivity Menu js loaded!'); // Voeg een event listener toe aan alle menu-items met het attribuut wp-action="toggleMenu" document.querySelectorAll('[wp-action="toggleMenu"]').forEach(function(element) { element.addEventListener('click', function(event) { // wp.hooks.doAction('toggleMenu', event, element); console.log('Toggle menu event:', event); toggleMenu(event, element); }); });

function toggleMenu(event, element) { event.preventDefault(); // Voorkom dat de link volgt

// Zoek het dichtstbijzijnde lijst-item
const parentLi = element.closest('li');
console.log('Parent li:', parentLi );

// Toggle de klasse 'menu-item-active' voor het tonen/verbergen van het submenu
parentLi.classList.toggle('menu-item-active');

}

// // Gebruik de WP Interactivity API om menu's te openen/sluiten // wp.hooks.addAction('toggleMenu', 'custom-interactivity-menu', function(event, element) { // event.preventDefault(); // Voorkom dat de link volgt

// // Zoek het dichtstbijzijnde lijst-item // const parentLi = element.closest('li'); // console.log('Parent li:', parentLi );

// // Toggle de klasse 'menu-item-active' voor het tonen/verbergen van het submenu // parentLi.classList.toggle('menu-item-active'); // });

`

Step 4: CSS for the Menu, this css statement is crucial for the multilevel menu: li.menu-item-active > ul.submenu ` / Generated by Custom Interactivity Menu /

/ voeg de style voor een nav en ul toe, deze moeten horizontaal zijn op desktop en tablet, verticaal op mobiel /

nav ul { display: flex; flex-direction: row; justify-content: space-between; list-style-type: none; padding: 0; margin: 0; }

/ voeg de style voor een menu-item toe, deze moet een margin hebben van 0 10px en een padding van 10px 20px / .menu-item { margin: 0 10px; padding: 10px 20px; }

@media screen and (max-width: 768px) { nav ul { flex-direction: column; } }

ul.submenu { display: none; }

/ het top level submenu moet display: block hebben, maar de onderliggende submenu's moeten display none houden / / ook, het submenu moet over de navigatie header heen vallen / li.menu-item-active > ul.submenu { display: block; position: absolute; background-color: white; z-index: 100; }

.has-submenu > a::after { content: ' ▼'; / Pijltje om aan te geven dat er een submenu is / }

`

Step 5: Shortcode for use in your Theme [interactivity_menu]

Step 6: add a nav walker class, so you can support multilevel menus: I added an includes folder in the plugin directory with the following class

Custom_Interactivity_Walker_Nav_Menu.txt

Then you have to make sure that your functions.php file has the following support: register_nav_menus( array( 'primary' => __( 'Primary Menu', 'trimae_theme' ), 'secondary' => __( 'Secondary Menu', 'trimae_theme' ), ) ); AND that wp_nav_menu has the right 'theme_location' => 'primary',

And you're menu looks something like this: menu-expanded

After overlooking this code, I thought why use the WP Interactivity anyway? I changed the js file and commented out wp.hooks.doAction('toggleMenu', event, element); and replaced it with a simple javascript function toggleMenu(event, element) { } and it still does the job.

I asked ChatGPT again, here is the answer:

_Seems like wp-interactivity is a bit cumbersome, don't you agree ;-) ChatGPT answered:

I totally agree! In many cases, like the one you're working on, the WP Interactivity API can feel overly cumbersome and add unnecessary complexity. While it was introduced to streamline certain interactive behaviors in WordPress themes, for simple tasks like toggling a multilevel menu, custom JavaScript is often a more straightforward and efficient approach.

The WP Interactivity API might be more useful for scenarios where you need to tie into WordPress' internal hook system for dynamic content updates or when building more complex, component-driven interfaces. However, for something as basic as menu toggling, directly handling it with JavaScript is often cleaner, more flexible, and easier to maintain.

In short: WP Interactivity API is helpful in specific, more complex use cases, but for straightforward UI interactions, going with custom JavaScript is usually the better and more practical route.

Glad you figured out the simpler path!_

Anyway, hope this helps and with our friend ChatGPT you can probably get the things done ;-)

nisception commented 1 month ago

I totally agree! In many cases, like the one you're working on, the WP Interactivity API can feel overly cumbersome and add unnecessary complexity. While it was introduced to streamline certain interactive behaviors in WordPress themes, for simple tasks like toggling a multilevel menu, custom JavaScript is often a more straightforward and efficient approach.

Like a lot of things within Gutenberg. Adding complexity without being consistent. Examples? The navigation block itself. It's a mess. Well intended, but a mess. It behaves like a unicorn within the block eco-system. I think the whole approach to that specific block is wrong. Better off writing a custom block to get things running like in other builders without a new hassle behind every corner. (Don't get me wrong, a lot of things got better in Gutenberg over the years, but I don't think anyone has a greater vision for that editor that matches with modern needs. Still, half of new clients ask us to switch to E****.)