[Magento 2] Display category image in navigation menu

[Edited] I’ve updated the article to do this in a better way

Recently I had to do something I thought it was simple to do: add the category images to the navigation menu. However, Magento doesn’t use a template to draw the whole menu but a function that will retrieve all categories and generate the ul/li tree.

In Magento 2.1.X you can do this rewriting the _getHtml() function from the class Magento\Theme\Block\Html\Topmenu. This is a function that calls itself once and again. We’ll create an observer that will add our code to the navigation menu.

1. Create your module

Create a foder in app/code/Vendor/NavigationMenu, where ‘vendor’ is your namespace for your modules.
app/code/Vendor/NavigationMenu/etc/module.xml

<?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="Vendor_NavigationMenu" setup_version="0.0.1"/></config>

Now the module registration:

<?php
/**
 * Registration file
 *
 * @category  Vendor
 * @package   Vendor\NavigationMenu
 * @author    Your Name <your@name.com>
 * @copyright 2017 Vendor
 * @license   http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Vendor_NavigationMenu',
    __DIR__
);

 

2. Rewrite the class Magento\Theme\Block\Html\TopMenu

Create the di.xml to declare the rewriting of the class where the _getHtml() method is called:
app/code/Vendor/NavigationMenu/etc/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Theme\Block\Html\Topmenu" type="Vendor\NavigationMenu\Rewrite\Block\Html\Topmenu" />
</config>

Now you can create the class that will rewrite the method. Now the following points:

  • We will add a custom event and an observer to that class. That way we can easily plug whatever we need to the menu.
  • The actual image addition will be done in the observer.
  • We had to add some warning supressions, because the _getHtml() Magento native function is not compliant to some coding standards

app/code/Vendor/NavigationMenu/Rewrite/Block/Html/Topmenu.php

<?php
/**
 * Vendor Project
 * Module Vendor/NavigationMenu
 *
 * @category  Vendor
 * @package   Vendor\NavigationMenu
 * @author    Your Name <your.name@email.com>
 * @copyright 2017 Vendor
 * @license   http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */
namespace Vendor\NavigationMenu\Rewrite\Block\Html;

use Magento\Framework\Data\Tree\Node;
use Magento\Framework\DataObject;
use Magento\Framework\View\Element\Template;

/**
 * Plugin NavigationMenu
 *
 * @author    Your Name <your.name@email.com>
 * @copyright 2017 Vendor
 */
class Topmenu extends \Magento\Theme\Block\Html\Topmenu
{
    /**
     * Recursively generates top menu html from data that is specified in $menuTree
     *
     * @param Node   $menuTree          menu tree
     * @param string $childrenWrapClass children wrap class
     * @param int    $limit             limit
     * @param array  $colBrakes         column brakes
     * @return string
     *
     * @SuppressWarnings(PHPMD)
     */
    protected function _getHtml(
        Node $menuTree,
        $childrenWrapClass,
        $limit,
        $colBrakes = []
    ) {
        $html = parent::_getHtml($menuTree, $childrenWrapClass, $limit, $colBrakes = []);

        $transportObject = new DataObject(['html' => $html, 'menu_tree' => $menuTree]);
        $this->_eventManager->dispatch(
            'vendor_topmenu_node_gethtml_after',
            ['menu' => $this->_menu, 'transport' => $transportObject]
        );

        $html = $transportObject->getHtml();

        return $html;
    }
}

3. Create an observer triggered by the event

For this, we’ll need to create the events.xml configuration file and assign the class that will handle the event:

app/code/Vendor/NavigationMenu/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="vendor_topmenu_node_gethtml_after">
        <observer name="vendor_navigationmenu_topmenu" instance="Vendor\NavigationMenu\Observer\AddContentToCategoryTopmenu" />
    </event>
</config>

app/code/Vendor/NavigationMenu/Observer/AddContentToCategoryTopmenu.php

<?php
/**
 * Topmenu catalog observer to add custom additional elements
 *
 * @category  Vendor
 * @package   Vendor\NavigationMenu
 * @author    Your Name <your.name@email.com>
 * @copyright 2017 Vendor
 * @license   http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */
namespace Vendor\NavigationMenu\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Catalog\Api\CategoryRepositoryInterface;

/**
 * Class AddFirstCategoryImageToTopmenu
 * @package Vendor\NavigationMenu
 */
class AddContentToCategoryTopmenu implements ObserverInterface
{
    /**
     * @var CategoryRepositoryInterface $categoryRepository
     */
    protected $categoryRepository;

    /**
     * AddFirstCategoryImageToTopmenu constructor.
     *
     * @param CategoryRepositoryInterface $categoryRepository repository
     */
    public function __construct(
        CategoryRepositoryInterface $categoryRepository
    ) {
        $this->categoryRepository = $categoryRepository;
    }

    /**
     * @param Observer $observer Observer object
     */
    public function execute(Observer $observer)
    {
        $transport = $observer->getTransport();
        $html      = $transport->getHtml();
        $menuTree  = $transport->getMenuTree();

        $parentLevel = $menuTree->getLevel();
        $childLevel = $parentLevel === null ? 0 : $parentLevel + 1;

        $menuId = $menuTree->getId();

        if ($childLevel == 1 && $this->isCategory($menuId)) {
            $html .= '<li class="category_image" style=""><img src="'.$this->getCategoryImage($menuId).'"/></li>';
        }

        $transport->setHtml($html);
    }

    /**
     * Retrieves the category image for the corresponding child
     *
     * @param string $categoryId Category composed ID
     *
     * @return string
     */
    protected function getCategoryImage($categoryId)
    {
        $categoryIdElements = explode('-', $categoryId);
        $category           = $this->categoryRepository->get(end($categoryIdElements));
        $categoryName       = $category->getImageUrl();

        return $categoryName;
    }

    /**
     * Check if current menu element corresponds to a category
     *
     * @param string $menuId Menu element composed ID
     *
     * @return string
     */
    protected function isCategory($menuId)
    {
        $menuId = explode('-', $menuId);

        return 'category' == array_shift($menuId);
    }
}

3 comments

    • Sandro on March 27, 2017 at 11:06 am

    Reply

    Hi Pau,

    I have done everything according to your post, yet my Navbar disappears completely, instead of displaying the category images..
    Any thoughts?
    Thanks for your efforts

      • Pau on April 22, 2017 at 3:33 am
      • Author

      Reply

      Hi Sandro,

      I need to know more about the problem you’re having. Actually, as you can see in my post, we only add the and extra html element to the navigation menu. If you don’t see that extra element and your menu stops working, it might simply means an error in your code. The rewrite I propose in this post is quite simple, so check the Magento logs or apache logs to see where the error is.

    • Franco on May 5, 2017 at 3:35 pm

    Reply

    I’m experiencing the same issue as Sandro ! :S Any clues?

Leave a Reply

Your email address will not be published.