custom widget in magento 2

Create Custom Widget in Magento 2

Posted by

In this article, we’ll create a custom widget that we can use in CMS pages, blocks, and even template files. Widgets play a crucial role in content management systems by allowing users to easily add and customize functionality across multiple areas of a website. Not just in Magento but they are in other CMS platform like WordPress.

What is Widget

A widget is some set of code which can generate dynamic content on your store. It provide user-friendly interface for the admin user to create dynamic content on the webpage. This dynamic content could be listing of specific products or generating an affiliate link or could be a third party content such as google reviews.

How it’s different from Block

A widget is similar to a static Block but not entirely. While both blocks and widgets are used to manage content in Magento, blocks are primarily used by developers to define page structure and content within layout XML files, while widgets are a user-friendly tool for non-technical users to add and configure content through the Admin Panel.

Create custom Widget

In our example we will create custom widget which will be used by an admin user to list products in a slider. These product will be based on the different filters such as best selling products, most viewed and recently added ones. These option to select will be given inside the widget settings. Lets begin our development process where we will start with the required files needed to create a widget and followed by other files which are needed only for this project.

The two main files to create custom model are module.xml and registration.php. Follow our article which will help to create these two files Create Magento 2 Custom Module Development.

widget.xml

widget.xml file is used to define and configure custom widgets that can be added to CMS pages, static blocks, or product pages through the Admin Panel. This file specifies the settings and properties of the widget, such as its label, description, and parameters as well as the widget class which will be used to render the widget’s output.

<?xml version="1.0" ?>
<widgets xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Widget:etc/widget.xsd">
    <widget class="Learningmagento\Customwidget\Block\Widget\MostProducts" id="learningmagento_develop_product_list">
        <label>Most Products Widget</label>
        <description>
            This widget will give the admin user to select different types of products to be listed base on ordered like recently added, most viewed and most ordered.
        </description>
        <parameters>
            <parameter name="title" sort_order="10" visible="true" xsi:type="text">
                <label translate="true">Title</label>
            </parameter>
            <parameter name="number_of_products" sort_order="20" visible="true" xsi:type="text">
                <label translate="true">No. of Products</label>
            </parameter>
            <parameter name="list_type" sort_order="30" visible="true" source_model="Learningmagento\Customwidget\Model\Config\Source\ProductType" xsi:type="select">
                <label translate="true">Product Type</label>
            </parameter>
        </parameters>
    </widget>
</widgets>

Block class

The block class which we defined for our custom widget does the main job. It checks what was the settings done for the widget by the admin user. For our case if the admin had selected the list type as most viewed then the method getLoadedProductCollection check the config value of widget and call appropriate function which will give product collection. We have added the template file where we will be writing html code to show the end users.

Note:- The extends class mostly is a block class Magento\Framework\View\Element\Template we used Magento\Catalog\Block\Product\ListProduct for our custom work to list products in slider. Recommend to use the Template class. The widget class must implement Magento\Widget\Block\BlockInterface.

<?php

/**
 *
 * @category  Custom Development
 * @email     contactus@learningmagento.com
 * @author    Learning Magento
 * @website   learningmagento.com
 * @Date      19-04-2024
 */
namespace Learningmagento\Customwidget\Block\Widget;

use Magento\Catalog\Api\CategoryRepositoryInterface;
use Magento\Catalog\Block\Product\Context;
use Magento\Catalog\Helper\Output as OutputHelper;
use Magento\Catalog\Model\Layer\Resolver;
use Magento\Framework\Data\Helper\PostHelper;
use Magento\Framework\Url\Helper\Data;

class MostProducts extends \Magento\Catalog\Block\Product\ListProduct implements \Magento\Widget\Block\BlockInterface
{
    protected $_template = 'Learningmagento_Customwidget::widget/most_products.phtml';

    protected $productReport;

    protected $productCollection;

    protected $bestSellersCollectionFactory;

    public function __construct(
        Context $context,
        PostHelper $postDataHelper,
        Resolver $layerResolver,
        CategoryRepositoryInterface $categoryRepository,
        Data $urlHelper,
        \Magento\Reports\Model\ResourceModel\Product\CollectionFactory $productReport,
        \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollection,
        \Magento\Sales\Model\ResourceModel\Report\Bestsellers\CollectionFactory $bestSellersCollectionFactory,
        array $data = [],
        ?OutputHelper $outputHelper = null
    )
    {
        $this->productReport = $productReport;
        $this->productCollection = $productCollection;
        $this->bestSellersCollectionFactory = $bestSellersCollectionFactory;
        parent::__construct($context, $postDataHelper, $layerResolver, $categoryRepository, $urlHelper, $data, $outputHelper);
    }

    public function getLoadedProductCollection() {
        $listType = $this->getData('list_type');
        if($listType == "best_sell") {
            return $this->getBestSelling();
        }
        else if($listType == "most_viewed") {
            return $this->getMostViewed();
        }
        else if($listType == "recently_added") {
            return  $this->getRecentlyAdded();
        }
    }


    protected function getBestSelling()
    {
        $bestSellers = $this->bestSellersCollectionFactory->create()
            ->setPeriod('year');
        $productIds = [];
        foreach ($bestSellers as $product) {
            $productIds[] = $product->getProductId();
        }
        $collection = $this->productCollection->create()->addIdFilter($productIds);
        $collection->addAttributeToSelect("*")
            ->setPageSize($this->getLimit())
            ->addStoreFilter($this->getStoreId());
        return $collection;
    }

    protected function getMostViewed()
    {
        $mostviewed =  $this->productReport->create()
            ->addAttributeToSelect('*')
            ->addViewsCount()
            ->setStoreId($this->getStoreId())
            ->addStoreFilter($this->getStoreId());

        $productIds = [];
        foreach ($mostviewed as $product) {
            $productIds[] = $product->getProductId();
        }
        $collection = $this->productCollection->create()->addIdFilter($productIds);
        $collection->addAttributeToSelect("*")
            ->setPageSize($this->getLimit())
            ->addStoreFilter($this->getStoreId());
        return $collection;
    }

    protected function getRecentlyAdded()
    {
        return $this->productCollection->create()
            ->addAttributeToSelect("*")
            ->addStoreFilter($this->getStoreId())
            ->setPageSize($this->getLimit())
            ->addAttributeToSort('entity_id', 'desc');
    }

    protected function getLimit()
    {
        return (int) $this->getData("number_of_products");
    }

    protected function getStoreId()
    {
        return $this->_storeManager->getStore()->getId();
    }
}

template file

Lastly the important file which is need to create a simple custom widget is a template file linked from the block class. We have used the below code from Webkul blog website of article Create custom product slider in magento 2. The file is created on path Learningmagento\Customwidget\view\frontend\templates\widget. We named it as most_products.phtml

<?php

/**
 *
 * @category  Custom Development
 * @email     contactus@learningmagento.com
 * @author    Learning Magento
 * @website   learningmagento.com
 * @Date      19-04-2024
 */

use Magento\Framework\App\Action\Action;
$_productCollection = $block->getLoadedProductCollection();
if ($_productCollection->getSize() <= 0) {
return;
}
?>
<h2><center><?php echo $block->getData("title"); ?></center></h2>
<div class="container-">
    <?php $_helper = $this->helper('Magento\Catalog\Helper\Output'); ?>
    <?php
    $viewMode = 'grid';
    $image = 'category_page_grid';
    $showDescription = false;
    $templateType = \Magento\Catalog\Block\Product\ReviewRendererInterface::SHORT_VIEW;
    $pos = $block->getPositioned();
    $position = '';
    $formKey = $block->getFormKey();
    ?>
    <style>
        .products-grid .product-item-inner{
            margin:unset;
        }
    </style>
    <?php $count = 1; ?>
    <div class="ind-container">
        <div class=" products wrapper grid products-grid <?php /* @escapeNotVerified */ echo $viewMode; ?> products-<?php /* @escapeNotVerified */ echo $viewMode; ?>">
            <?php $iterator = 1; ?>
            <div class="products grid items product-items slick-carousel" >
                <?php $prdCount = 0; ?>
                <?php foreach($_productCollection as $_product):

                    ?>
                    <?php //$_product = $block->getLoadProduct($_product->getEntityId()); ?>
                    <div class="item product product-item">
                        <div class="product-item-info item" data-container="product-grid">
                            <?php
                            $productImage = $block->getImage($_product, $image);
                            if ($pos != null) {
                                $position = ' style="left:' . $productImage->getWidth() . 'px;'
                                    . 'top:' . $productImage->getHeight() . 'px;"';
                            }
                            ?>
                            <?php // Product Image ?>
                            <a href="<?php /* @escapeNotVerified */ echo $_product->getProductUrl() ?>" class="product photo product-item-photo" tabindex="-1">
                                <?php /* @escapeNotVerified */ echo $productImage->toHtml(); ?>
                                <div class="product-discount">
                                    <?php $originalPrice = $_product->getPrice();
                                    $finalPrice = $_product->getFinalPrice();
                                    $discount = 0;
                                    if($originalPrice > $finalPrice){
                                        $discount = number_format(($originalPrice - $finalPrice) * 100 / $originalPrice,0);
                                        ?>
                                        <div class="price_section">
                                            <span class=""><?= $discount.'% Off'; ?></span>
                                        </div>
                                    <?php } ?>
                                    <a href="#"
                                       class="action towishlist"
                                       title="<?php echo $block->escapeHtml(__('Add to Wishlist')); ?>"
                                       aria-label="<?php echo $block->escapeHtml(__('Add to Wishlist')); ?>"
                                       data-post='<?php /* @escapeNotVerified */ echo $block->getAddToWishlistParams($_product); ?>'
                                       data-action="add-to-wishlist"
                                       role="button">
                                        <span><?php /* @escapeNotVerified */ echo __('Add to Wishlist') ?></span>
                                    </a>
                                </div>
                            </a>
                            <div class="product details product-item-details">
                                <?php $_productNameStripped = $block->stripTags($_product->getName(), null, true); ?>
                                <strong class="product name product-item-name">
                                    <a class="product-item-link"
                                       href="<?php /* @escapeNotVerified */ echo $_product->getProductUrl() ?>">
                                        <?php /* @escapeNotVerified */ echo $_helper->productAttribute($_product, $_product->getName(), 'name'); ?>
                                    </a>
                                </strong>
                                <?php /* @escapeNotVerified */ echo $block->getProductPrice($_product);?>
                                <?php /* @escapeNotVerified */ echo $block->getReviewsSummaryHtml($_product, $templateType); ?>

                                <div class="product-item-inner">
                                    <div class="product actions product-item-actions"<?php /* @escapeNotVerified */ echo $position; ?>>
                                        <div class="actions-primary"<?php /* @escapeNotVerified */ echo $position ; ?>>
                                            <?php if ($_product->isSaleable()): ?>
                                                <?php $postParams = $block->getAddToCartPostParams($_product); ?>
                                                <form data-role="tocart-form"
                                                      data-product-sku="<?= $escaper->escapeHtml($_product->getSku()) ?>"
                                                      action="<?= $escaper->escapeUrl($postParams['action']) ?>"
                                                      method="post"
                                                >
                                                    <input type="hidden"
                                                           name="product"
                                                           value="<?= /* @noEscape */ $postParams['data']['product'] ?>">
                                                    <input type="hidden"
                                                           name="<?= /* @noEscape */ Action::PARAM_NAME_URL_ENCODED ?>"
                                                           value="<?=
                                                           /* @noEscape */ $postParams['data'][Action::PARAM_NAME_URL_ENCODED]
                                                           ?>">
                                                    <input type="hidden" name="form_key" value="<?= /* @noEscape */ $formKey ?>">

                                                    <button type="submit"
                                                            title="<?php echo $block->escapeHtml(__('Add to Cart')); ?>"
                                                            class="action tocart primary">
                                                        <span><?php /* @escapeNotVerified */ echo __('Add to Cart') ?></span>
                                                    </button>
                                                </form>
                                            <?php else: ?>
                                                <?php if ($_product->getIsSalable()): ?>
                                                    <div class="stock available"><span><?php /* @escapeNotVerified */ echo __('In stock') ?></span></div>
                                                <?php else: ?>
                                                    <div class="stock unavailable"><span><?php /* @escapeNotVerified */ echo __('Out of stock') ?></span></div>
                                                <?php endif; ?>
                                            <?php endif; ?>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <script type="text/x-magento-init">
                        {
                            "[data-role=tocart-form], .form.map.checkout": {
                                "catalogAddToCart": {
                                    "product_sku": "<?= /* @noEscape */ $_product->getSku() ?>"
                                }
                            }
                        }
                        </script>
                    </div>
                <?php endforeach; ?>
            </div>
        </div>
    </div>
    <?php $count++; ?>
    <script>
        require(['jquery', 'jquery/ui', 'slick'], function($) {
            $(document).ready(function() {
                $(".slick-carousel").slick({
                    dots: true,
                    infinite: false,
                    slidesToShow: 4,
                    slidesToScroll: 1
                });
            });
        });
    </script>

Model class for Type

The below ProductType class is an optional file for our custom widget we have created it for our example for selecting different types of listing. This file is added in widget.xml file. The file location is in Learningmagento\Customwidget\Model\Config\Source.

<?php

/**
 *
 * @category  Custom Development
 * @email     contactus@learningmagento.com
 * @author    Learning Magento
 * @website   learningmagento.com
 * @Date      19-04-2024
 */
namespace Learningmagento\Customwidget\Model\Config\Source;

class ProductType implements \Magento\Framework\Data\OptionSourceInterface
{

    /**
     * Return array of options as value-label pairs
     *
     * @return array Format: array(array('value' => '<value>', 'label' => '<label>'), ...)
     */    public function toOptionArray()
    {
        return [
            ['value' => 'best_sell', 'label' => __('Best Selling of Month')],
            ['value' => 'most_viewed', 'label' => __('Most viewed')],
            ['value' => 'recently_added', 'label' => __('Recently Added')]
        ];
    }
}

This should add our custom widget inside magento. Following will show how we can add the widget not just the custom one but other predefined inside magento.

Adding custom widget on Page

To add the newly created custom widget, go to Admin -> Content -> Pages. Add Page or edit an existing page. For the page builder you need to add the Element HTML code. You will see a textarea right above it you will get a button insert widget. Click on it.

inserting custom widget
recently added products

Add the configuration for the custom widget and click insert. You will notice below code.

{{widget type="Learningmagento\Customwidget\Block\Widget\MostProducts" title="Recently Added Products" number_of_products="5" list_type="recently_added"}}

This should added the slider on the newly created or edited page.

custom widget show on frontend

Adding on static Blocks

To added the custom widget in a static block is same like added it on a Page. Go to Admin -> Content -> Block. Add or edit an existing block. Add the HTML code on the page builder and follow the same process like in page. It will added the below code in HTML edit area.

{{widget type="Learningmagento\Customwidget\Block\Widget\MostProducts" title="Recently Added Products" number_of_products="5" list_type="recently_added"}}

Adding custom widget in Code

To add the custom widget in the code is a job of a developer. He need to add it in any template files. Below code need will add our widget in the phtml template files.

<?php echo $this->getLayout()->createBlock("Learningmagento\Customwidget\Block\Widget\MostProducts")->setTitle("Recently Added")->setNumberOfProducts("5")->setListingType("recently_added")->toHtml(); ?>

In build Widget

The below are most commonly used widget in Magento and Adobe commerce.

  1. CMS Static Block: This widget allows users to insert a static block of content created in the Magento Admin Panel into any page or block.
  2. CMS Page Link: With this widget, users can link to a specific CMS page within their store.
  3. Catalog Product Link: Users can insert links to specific products from their catalog using this widget.
  4. Catalog New Products List: This widget displays a list of newly added products from the catalog.
  5. Recently Viewed Products: This widget displays a list of products that the customer has recently viewed.

Note

A custom widget is very rarely done by a developer. Mostly a developer prefers creating a block or view model to solve the problem. It is important to use the right solution for their client. Creating a widget gives their client ability to make changes as they want. To learn more about our Magento development check out our category Development.