4

I am wondering how to add programmatically a new link (to main top menu in header) with submenu (custom content visible on hover - styles is not a problem).

How to achieve that, via xml or via (php) plugin?

More descriptive image below:

enter image description here

1

5 Answers 5

7

You can add elements to the top menu using the event page_block_html_topmenu_gethtml_before.

Step 1 - So you need to create a module with these files (all the files should be in app/code/[Namespace]/[Module]):

Step 2 - app/code/[Namespace]/[Module]/etc/module.xml - the module declaration file

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="[Namespace]_[Module]" setup_version="2.0.0">
        <sequence>
            <module name="Magento_Theme"/>
        </sequence>
    </module>
</config>

Step 3 - app/code/[Namespace]/[Module]/registration.php - the registration file

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    '[Namespace]_[Module]',
    __DIR__
);

Step 4 - app/code/[Namespace]/[Module]/etc/frontend/events.xml - the events declaration file

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="page_block_html_topmenu_gethtml_before">
        <observer name="[namespace]_[module]_observer" instance="[Namespace]\[Module]\Observer\Topmenu" />
    </event>
</config>

Step 5 - app/code/[Namespace]/[Module]/Observer/Topmenu.php - the actual observer

<?php
namespace [Namespace]\[Module]\Observer;
use Magento\Framework\Event\Observer as EventObserver;
use Magento\Framework\Data\Tree\Node;
use Magento\Framework\Event\ObserverInterface;
class Topmenu implements ObserverInterface
{
    public function __construct(
        ...//add dependencies here if needed
    )
    {
    ...
    }
    /**
     * @param EventObserver $observer
     * @return $this
     */
    public function execute(EventObserver $observer)
    {
        /** @var \Magento\Framework\Data\Tree\Node $menu */
        $menu = $observer->getMenu();
        $tree = $menu->getTree();
        $data = [
            'name'      => __('Menu item label here'),
            'id'        => 'some-unique-id-here',
            'url'       => 'url goes here',
            'is_active' => (expression to determine if menu item is selected or not)
        ];
        $node = new Node($data, 'id', $tree, $menu);
        $menu->addChild($node);
        return $this;
    }
}
6
  • great tip, it's definitely a more readable approach than using php plugins here - thanks! A link is visible in top links menu but I am still not sure how to add content (submenu) which will be visible on hover. I found _addSubMenu function in Magento\Theme\Block\Html\Topmenu but I don't now how to use it correctly with created observer. Do you have any idea?
    – Rob
    Commented Feb 7, 2018 at 8:58
  • Do you also want to add child menus to it?I haven't tried adding child menus though but you can try adding submenus like $node->addChild($childNode); the $childnode can be created similar to how $node was created. Refer this class Magento\Framework\Data\Tree\Node for method definition and other tweaks
    – Shireen N
    Commented Feb 7, 2018 at 9:04
  • ok - got it, I will try, but do you have any idea how to add a child, node with html content which will be presented instead of the simple link?
    – Rob
    Commented Feb 7, 2018 at 9:33
  • It is possible to do that but it will take time to develop. Why don't you check this free extension if it suits your requirement - store.ibnab.com/free-magento-2-easy-megamenu.html
    – Shireen N
    Commented Feb 7, 2018 at 9:36
  • already I am working with custom top menu implementation so using next extension here will be problematic to maintain. Anyway, your answer with comments help me a lot and cover my question - thanks once again! I will try to update a bit _getHtml function in Magento\Theme\Block\Html\Topmenu to reach own goal :)
    – Rob
    Commented Feb 7, 2018 at 9:49
1

We can achieve this goal by overwrite default Magento file vendor/magento/module-theme/view/frontend/templates/html/topmenu.phtml. But this is not good practice by making changes in default Magento functionality. We will use the plugin to implement this.

Step -1 We have to create app/code/VendorName/ModuleName/etc/di.xml file where we will define our plugin.

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <!-- Add Plugin for add custom link in navigation -->
    <type name="Magento\Theme\Block\Html\Topmenu">
        <plugin name="add_menu_item_plugin" type="VendorName\ModuleName\Plugin\Topmenu" sortOrder="10" disabled="false"/>
    </type>
</config>

Step -2 Now we have to create app/code/VendorName/ModuleName/Plugin/Topmenu.php

<?php

namespace Test\TestCustomMenu\Plugin;

class Topmenu
{
    /**
     * @param Context $context
     * @param array $data
     */
    public function __construct(
        \Magento\Customer\Model\Session $session
    ) {
        $this->Session = $session;
    }


    public function afterGetHtml(\Magento\Theme\Block\Html\Topmenu $topmenu, $html)
    {
        $swappartyUrl = $topmenu->getUrl('testCustomMenu/custommenu'); //here you can set link
        $magentoCurrentUrl = $topmenu->getUrl('*/*/*', ['_current' => true, '_use_rewrite' => true]);
        if (strpos($magentoCurrentUrl, 'testCustomMenu/custommenu') !== false) {
            $html .= "
";
        } else {
            $html .= "
";
        }
        $html .= "" . __("Custom Menu") . "";
        $html .= "
";
        return $html;
    }
}

Step -3 After adding above files please run following command :-

php bin/magento setup:di:compile
php bin/magento cache:flush
0

The simple way to do this is here given below. For example Here I am using Solwin/freego theme so I have to navigate

/httpdocs/app/design/frontend/Solwin/freego/Magento_Theme/templates/html/topmenu.phtml (it might be different in your case)

And here I can add as much as the menu I want with a Home link

<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
// @codingStandardsIgnoreFile
?>
<?php
/**
 * Top menu for store
 *
 * @see \Magento\Theme\Block\Html\Topmenu
 */
?>
<?php $columnsLimit = $block->getColumnsLimit() ? : 0; ?>
<?php $_menu = $block->getHtml('level-top', 'submenu', $columnsLimit) ?>
<?php
$helper = $this->helper( 'Solwin\Cpanel\Helper\Data' );
$baseUrl = $helper->getBaseUrl();
$curr_Url = $helper->getCurrentUrl();
$ishome = $helper->getIsHomePage();
$showhomelink = $helper->getEnablehomelink();
?>

<nav class="navigation" role="navigation">
    <ul data-mage-init='{"menu":{"responsive":true, "expanded":true, "position":{"my":"left top","at":"left bottom"}}}'>
        <?php if ($showhomelink) { ?>
        <li class="level0 level-top <?php if ($ishome) { ?> active <?php } ?>">
            <a class="level-top" href="<?php echo $baseUrl; ?>">
                    Home
                </a>
        </li>
        <?php } ?>
        <li class="level0 level-top">
            <a class="level-top" href="<?php echo $baseUrl; ?>about-us">
                    About us
                </a>
        </li>
        <?php /* @escapeNotVerified */ echo $_menu; ?>
        <li class="level0 level-top">
            <a class="level-top" href="<?php echo $baseUrl; ?>faqs">
                    FAQs
                </a>

        </li>
        <li class="level0 level-top">
            <a class="level-top" href="<?php echo $baseUrl; ?>contact-us">
                    Contact Us
                </a>
        </li>
        <li class="level0 level-top">
            <a class="level-top" href="<?php echo $baseUrl; ?>account">
                    My Account
                </a>
        </li>
    </ul>
</nav>
1
  • It's such a bad approach to put application logic into views. It's hard to maintain and extend, it's imposible to debugging and finally it's breaching of any coding standarts.
    – Kirill
    Commented Jan 18, 2019 at 13:18
0

you can get from this code.

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

then not got then you can check it via xml code. or not done then goto block and add topmenu.phtml path.

-3

How can we move cms link at the beginning of the menu? Is it possible to add home link in front of all other links? On line 228, vendor/magento/module-theme/Block/Html/Topmenu.php there is a method $child->setIsFirst($counter == 1);, but it does not work.

2
  • Please add answers in the answer section. To ask a question create a separate thread.
    – Ghulam.M
    Commented May 7, 2019 at 9:44
  • @Ghulam.M I thought it is related to this question suggested by StackExchange. Can you answer my question here? Commented May 8, 2019 at 11:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.