Magento – Magento 2.1-rc1 How to customize Topmenu Html Markup

event-observermagento-2.1magento2plugin

Like in Magento1, the Topmenu html structure is still pretty hardcoded.

To change the html markup of the submenus in 2.0, I added a plugin which altered the getHtml Method of Magento\Theme\Block\Html\Topmenu

This was already pretty hacky since I extended my Plugin from the Topmenu and then called the _getHtml Method from my instance instead of the original Topmenu instance and did not call the $proceed callback function.

Despite not calling $proceed beeing really rude, the only alternative I had in mind was setting a preference and overriding the _getSubMenu method. Here is the original plugin class:

<?php
namespace Bragento2\Base\Model\Theme\Html\Topmenu;

use Magento\Framework\DataObject;
use Magento\Theme\Block\Html\Topmenu;

class Plugin extends Topmenu
{
    public function aroundGetHtml(
        Topmenu $subject,
        $proceed,
        $outermostClass = '',
        $childrenWrapClass = '',
        $limit = 0
    ) {
        $this->_eventManager->dispatch(
            'page_block_html_topmenu_gethtml_before',
            ['menu' => $this->_menu, 'block' => $this]
        );

        $this->_menu->setOutermostClass($outermostClass);
        $this->_menu->setChildrenWrapClass($childrenWrapClass);

        $html = $this->_getHtml($this->_menu, $childrenWrapClass, $limit);

        $transportObject = new DataObject(['html' => $html]);
        $this->_eventManager->dispatch(
            'page_block_html_topmenu_gethtml_after',
            ['menu' => $this->_menu, 'transportObject' => $transportObject]
        );
        $html = $transportObject->getHtml();

        return $html;
    }

    /**
     * Add sub menu HTML code for current menu item
     *
     * @param \Magento\Framework\Data\Tree\Node $child
     * @param string $childLevel
     * @param string $childrenWrapClass
     * @param int $limit
     * @return string HTML code
     */
    protected function _addSubMenu($child, $childLevel, $childrenWrapClass, $limit)
    {
        $html = '';
        if (!$child->hasChildren()) {
            return $html;
        }

        $colStops = null;
        if ($childLevel == 0 && $limit) {
            $colStops = $this->_columnBrake($child->getChildren(), $limit);
        }

        $html .= '<div class="nav__flyout level' . $childLevel . '"><ul class="container">';
        $html .= $this->_getHtml($child, $childrenWrapClass, $limit, $colStops);
        $html .= '</ul></div>';

        return $html;
    }
}

Now to the real Problem:
In Magento 2.1, the category Items are not anymore added by an Observer, but a plugin on the getHtml Method. Since I also have a Plugin which does not call the $proceed method, the category items are not added anymore (So this won't work anymore and I need an alternative)

Next thing I tried was changing the preference to my own Topmenu block class. But since the plugin, that adds the category items is defined for the original Topmenu class, this also does not work.

To my question: How can I change the html markup of the topmenu's submenus in Magento 2.1?

Best Answer

You should use Observer

etc/frontend/events.xml

<?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_after">
        <observer name="slava_yurthev_page_block_html_topmenu_gethtml_after" instance="SlavaYurthev\Menu\Observer\Html\Topmenu" />
    </event>
</config>

Observer/Html/Topmenu.php

<?php
namespace SlavaYurthev\Menu\Observer\Html;

use \Magento\Framework\Event\Observer;
use \Magento\Framework\Event\ObserverInterface;

class Topmenu implements ObserverInterface {
    public function execute(Observer $observer) {
        $menu = $observer->getData('menu');
        $transportObject = $observer->getData('transportObject');
        $transportObject->setData('html', 'your html');
    }
}

And you will can render this.

Also You can Define Layout

private $layout;
public function __construct(\Magento\Framework\View\LayoutInterface $layout){
    $this->layout = $layout;
}

And Call Custom Block

$block = $this->layout->createBlock('SlavaYurthev\Menu\Block\Html\Topmenu');
$block->setMenu($menu); // pass menu tree

And replace with html in transport

$transportObject->setData('html', $block->toHtml());
Related Topic