How to make magento mobile menu collapse on all levels

By default, magento mobile menu is set to collapse only at the top level and not on all levels. To overcome this default feature one must override the _toggleMobileMode function that is implemented in the lib\web\mage\menu.js file.
Overriding or extending javascript functionality in magento should be done using mixins. A mixin is a class whose methods are added to, or mixed in, with another class. Mixins are declared in the mixins property in the requirejs-config.js configuration file located in your module’s or theme’s root folder.

In this case we will create a new requirejs-config.js file under our theme’s root folder: app/design/frontent/<Theme_Vendor>/<Theme_Name>

#File: app/design/frontent/<Theme_Vendor>/<Theme_Name>/requirejs-config.js

var config = {
    config: {
        mixins: {
            'mage/menu': {
                'Magento_Theme/js/menu-mixin': true
            }
        }
    },
};

In this file we are saying to magento to use a mixin for the mage/menu javascript module. This mixin is named menu-mixin.js and located under app/design/frontent/<Theme_Vendor>/<Theme_Name>/Magento_Theme/web/js. Note that the web directory is not mentioned in the requirejs-config.js file but it is added automatically by magento.

Now we need to create the menu-mixin.js file:

#File: app/design/frontent/<Theme_Vendor>/<Theme_Name>/Magento_Theme/web/js/menu-mixin.js

define([
   'jquery',
],
function($){
	return function() {
		$.widget('mage.custommenu', $.mage.menu, {
		   _toggleMobileMode: function () {
				var subMenus;

				$(this.element).off('mouseenter mouseleave');
				this._on({

					/**
					 * @param {jQuery.Event} event
					 */
					'click .ui-menu-item:has(a)': function (event) {
						var target;

						event.preventDefault();
						target = $(event.target).closest('.ui-menu-item');
						target.get(0).scrollIntoView();

						if (!target.hasClass('parent') || !target.has('.ui-menu').length) {
							window.location.href = target.find('> a').attr('href');
						}
					},

					/**
					 * @param {jQuery.Event} event
					 */
					'click .ui-menu-item:has(.ui-state-active)': function (event) {
						this.collapseAll(event, true);
					}
				});

				subMenus = this.element.find('.parent');
				$.each(subMenus, $.proxy(function (index, item) {
					var category = $(item).find('> a span').not('.ui-menu-icon').text(),
						categoryUrl = $(item).find('> a').attr('href'),
						menu = $(item).find('> .ui-menu');

					this.categoryLink = $('<a>')
						.attr('href', categoryUrl)
						.text($.mage.__('All %1').replace('%1', category));

					this.categoryParent = $('<li>')
						.addClass('ui-menu-item all-category')
						.html(this.categoryLink);

					if (menu.find('.all-category').length === 0) {
						menu.prepend(this.categoryParent);
					}

				}, this));
			}
		});
		
		return $.mage.custommenu;
	}
});

Because in this file we are just overriding the _toggleMobileMode, we start by copying the original function to our mixin file to then make the changes we need to implement the required functionality.

If you compare the updated function with the original function you can see that the level-top selector was replaced by parent in 2 places: line #25 and line #38.

The last step will be to collapse the menu by default. This is done by overriding the topmenu.phtml file with the following

#File: app/design/frontent/<Theme_Vendor>/<Theme_Name>/Magento_Theme/templates/html/topmenu.phtml

<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

/**
 * Top menu for store
 *
 * @var $block \Magento\Theme\Block\Html\Topmenu
 */

$columnsLimit = $block->getColumnsLimit() ?: 0;
$_menuHtml = $block->getHtml('level-top', 'submenu', $columnsLimit)
?>

<nav class="navigation" data-action="navigation">
    <ul data-mage-init='{"menu":{"responsive":true, "expanded":false, "position":{"my":"left top","at":"left bottom"}}}'>
        <?= /* @noEscape */ $_menuHtml?>
        <?= $block->getChildHtml() ?>
    </ul>
</nav>

Note the change to false in the value of the expanded attribute.

Now you just probably need to make some css changes so it looks great!

One comment

  1. It’s worth noting instead of overriding a core template, this can be added in the mixin:
    “`
    _create: function () {
    this._super();
    this.options.expanded = false;
    },
    “`

Leave a Reply

Your email address will not be published. Required fields are marked *