Magento2 | PWA | GraphQL

Display popup after product is added to cart and show suggested products to the user in slider view


In this post we wiil check how to Display popup after product is added to cart and show suggested products to the user in slider view.

We will  give admin configuration options to choose from best selling, Latest Products or Random products.

Let's start by creating custom module.

You can find complete module on Github at Magelearn_CartPopup




Add registration.php file in it:

<?php

use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(
    ComponentRegistrar::MODULE,
    'Magelearn_CartPopup',
    __DIR__
);

Add composer.json file in it:

{
    "name": "magelearn/module-cartpopup",
    "description": "display popup after product is added to cart and show suggested products to the user",
    "type": "magento2-module",
    "license": "OSL-3.0",
    "authors": [
        {
            "email": "info@mage2gen.com",
            "name": "Mage2Gen"
        },
        {
            "email": "vijaymrami@gmail.com",
            "name": "vijay rami"
        }
    ],
    "minimum-stability": "dev",
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Magelearn\\CartPopup\\": ""
        }
    }
}

Add etc/module.xml file in it:

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

Now first we will give some admin configuration to choose which products (best selling, Latest Products or Random products) will be display in slider and some other option.

Create app/code/Magelearn/CartPopup/etc/adminhtml/system.xml file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="cartpopup" translate="label" type="text" sortOrder="200" showInDefault="1" showInWebsite="1"
                 showInStore="1">
            <label>Add to Cart PopUp</label>
            <tab>catalog</tab>
            <resource>Magelearn_CartPopup::cartpopup</resource>
            <group id="settings" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Add To Cart PopUp</label>
                <field id="enabled" translate="label" type="select" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Enable/Disable Popup After Product Added to cart ?</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="product_carousel" translate="label" type="select" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Product Display</label>
                    <source_model>Magelearn\CartPopup\Model\Config\Source\Carousel</source_model>
                    <depends>
                        <field id="cartpopup/settings/enabled">1</field>
                    </depends>
                </field>
                <field id="carousel_autoplay" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Carousel Autoplay ?</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                    <depends>
                        <field id="cartpopup/settings/enabled">1</field>
                    </depends>
                </field>
                <field id="product_limit" translate="label" sortOrder="6" type="select" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
	               <label>Product Limit</label>
	               <source_model>Magelearn\CartPopup\Model\Config\Source\ProductLimit</source_model>
				   <comment>Set the number for products to show on Carousal block.</comment>
				   <depends>
                        <field id="cartpopup/settings/enabled">1</field>
                    </depends>
	            </field>
            </group>
        </section>
    </system>
</config>

Add app/code/Magelearn/CartPopup/etc/acl.xml file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
    <acl>
        <resources>
            <resource id="Magento_Backend::admin">
                <resource id="Magento_Backend::stores">
                    <resource id="Magento_Backend::stores_settings">
                        <resource id="Magento_Config::config" title="Configuration" translate="title" sortOrder="20">
                            <resource id="Magelearn_CartPopup::cartpopup" title="Add to cart popup" translate="title"
                                      sortOrder="150"/>
                        </resource>
                    </resource>
                </resource>
            </resource>
        </resources>
    </acl>
</config>

Add default value of configuration options at app/code/Magelearn/CartPopup/etc/config.xml file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <cartpopup>
            <settings>
                <enabled>1</enabled>
                <product_carousel>_randomProducts</product_carousel>
                <product_limit>10</product_limit>
                <carousel_autoplay>1</carousel_autoplay>
            </settings>
        </cartpopup>
    </default>
</config>

Now as per highlighted code in system.xml file, we will add app/code/Magelearn/CartPopup/Model/Config/Source/Carousel.php file.

<?php

namespace Magelearn\CartPopup\Model\Config\Source;

class Carousel implements \Magento\Framework\Option\ArrayInterface
{
    /**
     * Options getter
     *
     * @return array
     */
    public function toOptionArray()
    {
        return [
            ['value' => '_bestSellerProducts', 'label' => __('Best Sellers')],
            ['value' => '_latestProducts', 'label' => __('Latest Products')],
            ['value' => '_randomProducts', 'label' => __('Random Products')]
        ];
    }

    /**
     * Get options in "key-value" format
     *
     * @return array
     */
    public function toArray()
    {
        return [
            '_bestSellerProducts' => __('Best Sellers'),
            '_latestProducts' => __('Latest Products'),
            '_randomProducts' => __('Random Products')
        ];
    }
}

Also add app/code/Magelearn/CartPopup/Model/Config/Source/ProductLimit.php file.

<?php

namespace Magelearn\CartPopup\Model\Config\Source;

class ProductLimit implements \Magento\Framework\Option\ArrayInterface
{
    /**
     * Options getter
     *
     * @return array
     */
    public function toOptionArray()
    {
        return [
            ['value' => 4, 'label' => 4],
            ['value' => 6, 'label' => 6],
            ['value' => 8, 'label' => 8],
            ['value' => 10, 'label' => 10],
            ['value' => 12, 'label' => 12]
            
        ];
    }

    /**
     * Get options in "key-value" format
     *
     * @return array
     */
    public function toArray()
    {
        return [
            4 => 4,
            6 => 6,
            8 => 8,
            10 => 10,
            12 => 12
        ];
    }
}

Now we will add custom section in Customer section.

For that add app/code/Magelearn/CartPopup/etc/frontend/di.xml file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Customer\CustomerData\SectionPoolInterface">
        <arguments>
            <argument name="sectionSourceMap" xsi:type="array">
                <item name="cartpopup" xsi:type="string">Magelearn\CartPopup\CustomerData\PopupCartData</item>
            </argument>
        </arguments>
    </type>
</config>

As per defined in etc/frontend/di.xml file, we will add app/code/Magelearn/CartPopup/CustomerData/PopupCartData.php file.

<?php

namespace Magelearn\CartPopup\CustomerData;

use Magento\Catalog\Helper\Image;
use Magento\Catalog\Model\Product\Attribute\Source\Status;
use Magento\Catalog\Model\Product\Visibility;
use Magento\Catalog\Model\ResourceModel\Product\Collection;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\Checkout\Helper\Cart as CartHelper;
use Magento\Customer\CustomerData\SectionSourceInterface;
use Magento\Framework\Pricing\Helper\Data as PricingHelper;
use Magento\Sales\Model\ResourceModel\Report\Bestsellers\CollectionFactory as BestSellersCollectionFactory;
use Magelearn\CartPopup\Helper\Data as Helper;

/**
 * PopupCart source
 */
class PopupCartData implements SectionSourceInterface
{
    const CONFIG_PRODUCT_LIMIT = 'cartpopup/settings/product_limit';
    const CONFIG_COLLECTION_TYPE = 'cartpopup/settings/product_carousel';

    /**
     * @var Magento\Catalog\Model\ResourceModel\Product\CollectionFactory
     */
    protected $collectionFactory;

    /**
     * @var Magento\Catalog\Helper\Image
     */
    protected $catalogImage;

    /**
     * @var PricingHelper
     */
    protected $pricingHelper;

    /**
     * @var Status
     */
    protected $productStatus;

    /**
     * @var Visibility
     */
    protected $productVisibility;

    /** @var Collection */
    protected $collection;

    /**
     * @var \Magento\Checkout\Helper\Cart
     */
    protected $cartHelper;

    /**
     * @var Helper
     */
    protected $helper;

    /**
     * @var BestSellersCollectionFactory
     */
    protected $bestSellersCollectionFactory;

    /**
     * @param CollectionFactory $collectionFactory
     * @param Image $catalogImage
     * @param PricingHelper $pricingHelper
     * @param Status $productStatus
     * @param Visibility $productVisibility
     * @param CartHelper $cartHelper
     * @param Helper $helper
     * @codeCoverageIgnore
     */
    public function __construct(
        CollectionFactory $collectionFactory,
        Image $catalogImage,
        PricingHelper $pricingHelper,
        Status $productStatus,
        Visibility $productVisibility,
        CartHelper $cartHelper,
        Helper $helper,
        BestSellersCollectionFactory $bestSellersCollectionFactory
    )
    {
        $this->collectionFactory = $collectionFactory;
        $this->catalogImage = $catalogImage;
        $this->pricingHelper = $pricingHelper;
        $this->productStatus = $productStatus;
        $this->productVisibility = $productVisibility;
        $this->cartHelper = $cartHelper;
        $this->helper = $helper;
        $this->bestSellersCollectionFactory = $bestSellersCollectionFactory;

        $this->_initCollection();
    }

    /**
     * Return data for section "cartpopup"
     * @return array
     * @throws \Magento\Framework\Exception\LocalizedException
     * {@inheritdoc}
     */
    public function getSectionData()
    {
        $collectionType = $this->helper->getStoreConfig(self::CONFIG_COLLECTION_TYPE);
        $this->$collectionType();
        $output = [
            'cartTotalCount' => $this->cartHelper->getSummaryCount(),
            'products' => $this->_getCollection()
        ];

        return $output;
    }

    /**
     * Init Product Collection
     */
    private function _initCollection()
    {
        /**
         * I'm still using a collection here because currently
         * it is not possible to sort a repository result randomly.
         *
         * TODO: Remove collection and implement a repository
         */

        $this->collection = $this->collectionFactory->create();
        $this->collection->addAttributeToSelect('*');
        $this->collection->addStoreFilter();
        $this->collection->addAttributeToFilter(
            'status',
            ['in' => $this->productStatus->getVisibleStatusIds()]
        );
        $this->collection->addAttributeToFilter(
            'visibility',
            ['in' => $this->productVisibility->getVisibleInSiteIds()]
        );
        $this->collection->addUrlRewrite();
        $this->collection->addMinimalPrice();
    }

    /**
     * Select random products
     */
    private function _randomProducts()
    {
        $this->collection->getSelect()->orderRand();
    }

    /**
     * Select latest products
     */
    private function _latestProducts()
    {
        $this->collection->addAttributeToSort('entity_id', 'desc');
    }

    /**
     * Build Collection
     * @return array
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    private function _getCollection()
    {
        /**
         * Set collection limit
         * TODO: Create admin configuration to set this value and implement product carousel
         */
        $this->collection->setPageSize($this->helper->getStoreConfig(self::CONFIG_PRODUCT_LIMIT));
        foreach ($this->collection as $i => $product) {
            $product->setData('product_url', $product->getProductUrl());
            $product->setData(
                'product_image',
                $this->catalogImage->init($product, 'product_base_image')
                    ->getUrl()
            );
            $product->setData(
                'product_price',
                $this->pricingHelper->currency(
                    $product->getMinimalPrice(),
                    true,
                    false
                )
            );
            $this->collection->removeItemByKey($i);
            $this->collection->addItem($product);
        }

        return $this->collection->toArray();
    }

    /**
     * Create collection with best selling products of this month
     */
    private function _bestSellerProducts()
    {
        $productIds = [];
        $bestSellers = $this->bestSellersCollectionFactory->create()
            ->setPeriod('month');

        foreach ($bestSellers as $product) {
            $productIds[] = $product->getProductId();
        }

        if (empty($productIds)) {
            $this->_randomProducts();
        } else {
            $this->collection->addIdFilter($productIds);
        }
    }
}

We will also add app/code/Magelearn/CartPopup/etc/frontend/sections.xml file, so whenever product added to cart that custom section will also be updated.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Customer:etc/sections.xsd">
    <action name="checkout/cart/add">
        <section name="cartpopup"/>
    </action>
</config>

Now we will add app/code/Magelearn/CartPopup/Helper/Data.php  file.

<?php

namespace Magelearn\CartPopup\Helper;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Helper\AbstractHelper;
use Magento\Framework\App\Helper\Context;
use Magento\Store\Model\ScopeInterface;

class Data extends AbstractHelper
{
    /**
     * @var ScopeConfigInterface
     */
    protected $scopeConfig;

    /**
     * Data constructor.
     * @param Context $context
     * @param ScopeConfigInterface $scopeConfig
     */
    public function __construct(
        Context $context,
        ScopeConfigInterface $scopeConfig
    ) {
        parent::__construct($context);
        $this->scopeConfig = $scopeConfig;
    }

    /**
     * Get configuration from Store scope
     * @param $path
     * @return mixed
     */
    public function getStoreConfig($path)
    {
        /** @var String $path */
        return $this->scopeConfig->getValue($path, ScopeInterface::SCOPE_STORE);
    }
}

Now to display product in popup at frontend, we will first add app/code/Magelearn/CartPopup/view/frontend/layout/default.xml file.

<?xml version="1.0"?>
<page layout="3columns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="default_head_blocks"/>
    <head>
        <css src="Magelearn_CartPopup::css/cartpopup.css"/>
        <css src="Magelearn_CartPopup::css/owl.carousel.min.css"/>
    </head>
    <body>
        <referenceContainer name="content">
                <block class="Magelearn\CartPopup\Block\Popup" name="ajax.popup.placeholder"
                       ifconfig="cartpopup/settings/enabled" template="Magelearn_CartPopup::popup.phtml"/>
        </referenceContainer>
    </body>
</page>

Add app/code/Magelearn/CartPopup/view/frontend/templates/popup.phtml file.

<div data-placeholder="cartpopup" style="display: none;">
    <div data-bind="scope: 'cartpopup'">
        <!-- ko template: getTemplate() --><!-- /ko -->
    </div>
</div>
<script type="text/x-magento-init">
    {
        "*": {
            "Magento_Ui/js/core/app": {
                "components": {
                            "cartpopup": {
                                "component": "Magelearn_CartPopup/js/cartpopup",
                                "shoppingCartUrl": "<?php echo $block->getShoppingCartUrl(); ?>",
                                "cartMessage": "<?php echo $block->getCartMessage(); ?>",
                                "successIcon" : "<?php echo $block->getSuccessIcon(); ?>",
                                "carouselAutoPlay" : "<?php echo $block->getCarouselAutoPlay(); ?>"
                            }
                    }
                }
            }
    }
</script>

Add app/code/Magelearn/CartPopup/Block/Popup.php file.

<?php

namespace Magelearn\CartPopup\Block;

use Magento\Framework\View\Asset\Repository as AssetRepository;
use Magento\Framework\View\Element\Template;
use Magelearn\CartPopup\Helper\Data as Helper;

class Popup extends Template
{
    const CONFIG_CAROUSEL_AUTOPLAY = 'cartpopup/settings/carousel_autoplay';
    
    /** @var AssetRepository  */
    protected $assetRepository;
    
    /**
     * @var Helper
     */
    protected $helper;
    
    /**
     * Popup constructor.
     * @param Template\Context $context
     * @param AssetRepository $assetRepository
     * @param array $data
     */
    public function __construct(
        Template\Context $context,
        AssetRepository $assetRepository,
        Helper $helper,
        array $data = []
    ) {
        parent::__construct($context, $data);
        $this->assetRepository = $assetRepository;
        $this->helper = $helper;
    }

    /**
     * Get shopping cart url
     * @return string
     */
    public function getShoppingCartUrl()
    {
        return $this->getUrl('checkout/cart');
    }

    /**
     * Returns a message to be displayed on the cart popup
     * @return string
     */
    public function getCartMessage()
    {
        $message  = __('A new item has been added to your Shopping Cart. ');
        $message .= __('You now have %s items in your Shopping Cart.');

        return sprintf($message, "<span id='cart-popup-total-count'></span>");
    }

    /**
     * Returns an icon to be displayed
     * @return string
     */
    public function getSuccessIcon()
    {
        return $this->assetRepository->getUrl('Magelearn_CartPopup::images/success_icon.png');
    }
    /**
     * Returns an icon to be displayed
     * @return string
     */
    public function getCarouselAutoPlay()
    {
        return $this->helper->getStoreConfig(self::CONFIG_CAROUSEL_AUTOPLAY);
    }
}

Now we will add our JS component file at app/code/Magelearn/CartPopup/view/frontend/web/js/cartpopup.js file.

define([
    'jquery',
    'uiElement',
    'ko',
    'Magento_Customer/js/customer-data',
    'Magento_Ui/js/modal/modal',
    'owlCarousel'
], function ($, Component, ko, customerData, modal) {

    'use strict';

    var modalPopupSelector = '[data-placeholder="cartpopup"]';

    return Component.extend({
        defaults: {
            template: 'Magelearn_CartPopup/popup'
        },
        isUpdated: ko.observable(false),
        products: ko.observable(false),
        successIcon: ko.observable(),
        cartTotalCount: ko.observable(),
        cartMessage: ko.observable(),
        shoppingCartUrl: '',

        /**
         * Update modal popup content.
         *
         * @param {Object} updatedCart
         * @returns void
         */
        update: function (updatedCart) {
            this.products(updatedCart.products);
            this.applyCartMessageBinding();
            this.cartTotalCount(updatedCart.cartTotalCount);
            this.isUpdated(false);

            $(modalPopupSelector).modal("openModal");
        },

        /**
         * Close Modal Popup Action
         */
        closeModal: function () {
            $(modalPopupSelector).modal("closeModal");
        },

        /**
         * Redirect to Shopping Cart
         */
        viewShoppingCart: function () {
            window.location.replace(this.shoppingCartUrl);
        },

        /**
         * Apply bindings to element loaded inside of cartMessage
         */
        applyCartMessageBinding: function () {
            ko.applyBindingsToNode(
                document.getElementById(
                    'cart-popup-total-count'),
                {text: this.cartTotalCount}
            );
        },

        /**
         * Initialize Component
         * @returns {*}
         */
        initialize: function (config) {
            var self = this,
            cartPopupData = customerData.get('cartpopup');
			var carousal_autoplay = config.carouselAutoPlay;
            /**
             * Update CartPopup only when an addToCart action is triggered
             */
            $(document).on('ajax:addToCart', function (sku, productIds, form, response) {
                self.isUpdated(true);
            });

            /**
             * Subscribe to changes on the CustomerData component
             */
            cartPopupData.subscribe(function (updatedCart) {
                if (self.isUpdated()) {
                    self.update(updatedCart);
                }
            }, this);

            /**
             * Set Modal Popup Component options
             */
            var options = {
                type: 'popup',
                responsive: true,
                innerScroll: false,
                modalClass: 'no-header-footer',
                buttons: [{
                    text: $.mage.__('Close'),
                    class: '',
                    click: function () {
                        this.closeModal();
                    }
                }]
            };

            modal(options, $(modalPopupSelector));
			ko.bindingHandlers.owlCarouselInitiator = {
			    init: function() {
					console.log(carousal_autoplay);
					$('.owl-carousel').owlCarousel({
						loop:true,
				        nav:true,
				        navText:["<div class='nav-btn prev-slide'></div>","<div class='nav-btn next-slide'></div>"],
						autoplay:parseInt(carousal_autoplay),
						autoplayTimeout:2000,
						items : 4,
				      	itemsDesktop : [1000,4],
				      	itemsDesktopSmall : [900,4],
				      	itemsTablet: [600,2]
		        });
			    }
			};
            return self._super();
        }
    })
});

As per defined in cartpopup.js file, we will add our template file at app/code/Magelearn/CartPopup/view/frontend/web/template/popup.html file.

<div class="modal-popup-header">
    <div class="modal-popup-icon">
        <img data-bind="attr: { src: successIcon }" width="40" height="40">
    </div>
    <div class="modal-popup-top">
        <p data-bind="html: cartMessage"></p>
        <p class="cart-buttons">
            <button type="button" class="view-shopping-cart" data-bind="event: {click: viewShoppingCart}">
                <!-- ko i18n: 'View Shopping Cart' --><!-- /ko --></button>
            <button type="button" class="continue-shopping" data-bind="event: { click: closeModal }">
                <!-- ko i18n: 'Continue Shopping' --><!-- /ko --></button>
        </p>
    </div>
</div>
<div class="modal-popup-body">
    <p><!-- ko i18n: 'Other Items You Might Be Interested In:' --><!-- /ko --></p>
    <div data-bind="foreach: products">
        <ul data-bind="foreach:Object.keys($data), owlCarouselInitiator" class="modal-product-list owl-carousel">
            <li>
                <a data-bind="attr: { href: $parent[$data].product_url }">
                    <img data-bind="attr: { src: $parent[$data].product_image }" width="155" height="155" />
                </a>
                <div class="cartpopup-item">
                <span><strong data-bind="text: $parent[$data].product_price"></strong></span>
                </div>
            </li>
        </ul>
    </div>
</div>

At last we will add app/code/Magelearn/CartPopup/view/frontend/requirejs-config.js file to define path for owl carousel JS.

var config = {
	paths: {            
    	'owlCarousel': "Magelearn_CartPopup/js/owl.carousel.min"
    },
    shim: {
		'owlCarousel': {
	    	deps: ['jquery']
	    }
	}
}

We will add 

app/code/Magelearn/CartPopup/view/frontend/web/css/cartpopup.css

app/code/Magelearn/CartPopup/view/frontend/web/css/owl.carousel.min.css and app/code/Magelearn/CartPopup/view/frontend/web/js/owl.carousel.min.js  files and images at 

app/code/Magelearn/CartPopup/view/frontend/web/images folder

to display it properly on front end.

0 Comments On "Display popup after product is added to cart and show suggested products to the user in slider view"

Back To Top