Magento 2 – How to add custom thumbnail images in submenu items.

Adding images in sub-menu is a common feature for most of the online store. Today, I was working on this issue to extend the menu. So, I want to share my experience with you. To accomplish this feature you should follow the things below:

1. Create a basic module.

Register a basic module (Milandev_CustomMenu) by creating some basic files like etc/module.xml , composer.json, registration.php.

2. Add a category attribute.

Create a category attribute cat_thumbnail (notice the highlighted lines below) for thumbnail image. We will show it in the frontend menu items.
app/code/Milandev/CustomMenu/Setup/InstallData.php

<?php
namespace Milandev\CustomMenu\Setup;

use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;

class InstallData implements InstallDataInterface
{

    private $eavSetupFactory;

    /**
     * Constructor
     *
     * @param \Magento\Eav\Setup\EavSetupFactory $eavSetupFactory
     */
    public function __construct(EavSetupFactory $eavSetupFactory)
    {
        $this->eavSetupFactory = $eavSetupFactory;
    }

    /**
     * {@inheritdoc}
     */
    public function install(
        ModuleDataSetupInterface $setup,
        ModuleContextInterface $context
    ) {
        $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);

        $eavSetup->addAttribute(
            \Magento\Catalog\Model\Category::ENTITY,
            'cat_thumbnail',
            [
                'type' => 'varchar',
                'label' => 'Thumbnail',
                'input' => 'image',
                'sort_order' => 333,
                'source' => '',
                'global' => 1,
                'visible' => true,
                'required' => false,
                'user_defined' => false,
                'default' => null,
                'group' => 'General Information',
                'backend' => 'Magento\Catalog\Model\Category\Attribute\Backend\Image'
            ]
        );
    }
}

Add the category admin UI component for the created attribute above.
app/code/Milandev/CustomMenu/view/adminhtml/ui_component/category_form.xml

<?xml version="1.0" ?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
	<fieldset name="general">
		<field name="cat_thumbnail">
			<argument name="data" xsi:type="array">
				<item name="config" xsi:type="array">
					<item name="required" xsi:type="boolean">false</item>
					<item name="validation" xsi:type="array">
						<item name="required-entry" xsi:type="boolean">false</item>
					</item>
					<item name="sortOrder" xsi:type="number">333</item>
					<item name="dataType" xsi:type="string">string</item>
					<item name="formElement" xsi:type="string">fileUploader</item>
					<item name="label" translate="true" xsi:type="string">Thumbnail</item>
					<item name="uploaderConfig" xsi:type="array">
						<item name="url" path="catalog/category_image/upload" xsi:type="url"/>
					</item>
					<item name="elementTmpl" xsi:type="string">ui/form/element/uploader/uploader</item>
					<item name="previewTmpl" xsi:type="string">Magento_Catalog/image-preview</item>
				</item>
			</argument>
		</field>
	</fieldset>
</form>

3. Overwrite the default menu class with your module class.

In this section, you have to replace the default menu with your module menu. First, let’s overwrite the default menu class.
app/code/Milandev/CustomMenu/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="Milandev\CustomMenu\Block\Html\Topmenu" />
</config>

Now create your own module class. Actually, you need to extend the default menu class to your module class. Please notice the getCustomThumbnail method. Which is responsible for rendering the thumbnail image from categories.
app/code/Milandev/CustomMenu/Block/Html/Topmenu.php

<?php
namespace Milandev\CustomMenu\Block\Html;
 
use Magento\Framework\Data\Tree\Node;
use Magento\Framework\DataObject;
use Magento\Framework\View\Element\Template;
use Magento\Framework\Data\Tree\NodeFactory;
use Magento\Framework\Data\TreeFactory;
 
class Topmenu extends \Magento\Theme\Block\Html\Topmenu
{
    protected $_categoryFactory;
    protected $_storeManager;
    
    public function __construct(
        Template\Context $context,
        NodeFactory $nodeFactory,
        TreeFactory $treeFactory,
        \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $collecionFactory,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        array $data = []
        ) {
            parent::__construct($context, $nodeFactory, $treeFactory, $data);
            $this->_categoryFactory = $collecionFactory;
            $this->_storeManager = $storeManager;
        
    }

    protected function _getHtml(
        \Magento\Framework\Data\Tree\Node $menuTree,
        $childrenWrapClass,
        $limit,
        array $colBrakes = []
    ) {
        $html = '';

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

        $counter = 1;
        $itemPosition = 1;
        $childrenCount = $children->count();

        $parentPositionClass = $menuTree->getPositionClass();
        $itemPositionClassPrefix = $parentPositionClass ? $parentPositionClass . '-' : 'nav-';

        /** @var \Magento\Framework\Data\Tree\Node $child */
        foreach ($children as $child) {
            if ($childLevel === 0 && $child->getData('is_parent_active') === false) {
                continue;
            }
            $child->setLevel($childLevel);
            $child->setIsFirst($counter == 1);
            $child->setIsLast($counter == $childrenCount);
            $child->setPositionClass($itemPositionClassPrefix . $counter);

            $outermostClassCode = '';
            $outermostClass = $menuTree->getOutermostClass();

            if ($childLevel == 0 && $outermostClass) {
                $outermostClassCode = ' class="' . $outermostClass . '" ';
                $currentClass = $child->getClass();

                if (empty($currentClass)) {
                    $child->setClass($outermostClass);
                } else {
                    $child->setClass($currentClass . ' ' . $outermostClass);
                }
            }

            if (is_array($colBrakes) && count($colBrakes) && $colBrakes[$counter]['colbrake']) {
                $html .= '</ul></li><li class="column"><ul>';
            }
            
            $html .= '<li ' . $this->_getRenderedMenuItemAttributes($child) . '>';
            $html .= '<a href="' . $child->getUrl() . '" ' . $outermostClassCode . '><span>' . $this->escapeHtml(
                $child->getName()
                ) . $this->getCustomThumbnail($child) . '</span></a>' . $this->_addSubMenu(
                $child,
                $childLevel,
                $childrenWrapClass,
                $limit
            ) . '</li>';
            $itemPosition++;
            $counter++;
        }

        if (is_array($colBrakes) && count($colBrakes) && $limit) {
            $html = '<li class="column"><ul>' . $html . '</ul></li>';
        }

        return $html;
    }

    public function getCustomThumbnail($childObj)
    {
        if (!($childObj->getIsCategory() && $childObj->getLevel() == 1)) {
            return false;
        }

        $store = $this->_storeManager->getStore();
        $mediaBaseUrl = $store->getBaseUrl(
            \Magento\Framework\UrlInterface::URL_TYPE_MEDIA
        );

        $catNodeArr = explode('-', $childObj->getId());
        $catId = end($catNodeArr);
        
        $collection = $this->_categoryFactory
                ->create()
                ->addAttributeToSelect('cat_thumbnail')
                ->addAttributeToFilter('entity_id',['eq'=>$catId])
                ->setPageSize(1);
        
        if ($collection->getSize() && $collection->getFirstItem()->getCatThumbnail()) {
            $catThumbnailUrl = $mediaBaseUrl
                        . ltrim(\Magento\Catalog\Model\Category\FileInfo::ENTITY_MEDIA_PATH, '/')
                        . '/'
                        . $collection->getFirstItem()->getCatThumbnail();

            return '<span class="cat-thumbnail"><img src="'.$catThumbnailUrl.'"></span>';
        }
    }

}

You should add your own CSS file for styling purpose.

Happy Coding!

Leave a Reply

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