Reading list Switch to dark mode

    Magento 2 Custom Content in Configurable Product Media Gallery

    Updated 20 November 2023

    Hello Friends, in this blog, we will learn how can we add extra or custom content or media to Configurable Products’ Fotorama media gallery at the front end in visual and text swatches case.

    For dropdown swatch type, check our blog Add Custom Content in Configurable Product Media Gallery – Magento 2

    In Magento 2, on the configurable product page, for rendering the swatch attributes, <magento-root-dir.>/vendor/magento/module-swatches/view/base/web/js/swatch-renderer.js file is responsible.

    So, to add extra content in the configurable product’s media gallery(in text and visual swatch case), we need to override a few methods of the swatch-renderer js file.
    For example, updateBaseImage, _onGalleryLoaded and _loadMedia functions are responsible for updating the media gallery after swatch rendering and swatch selection.

    So, to add the custom content I have overridden the required functions and added some required files which are required to achieve our goal.
    First of all, we will create a demo module. Here, I have created the Webkul_ExtraContentInFotorama module. Further, follow the below steps:

    1. Create a di.xml file inside the <magento-root-dir>/app/code/Webkul/ExtraContentInFotorama/etc/ directory.

    <?xml version="1.0"?>
    <!--
    /**
     * Webkul Software.
     *
     * @category  Webkul
     * @package   Webkul_ExtraContentInFotorama
     * @author    Webkul Software Private Limited
     * @copyright Webkul Software Private Limited (https://webkul.com)
     * @license   https://store.webkul.com/license.html
     */
    -->
    <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
      <!--Add Extra Configuration Data In JsonConfig-->
      <type name="Magento\ConfigurableProduct\Block\Product\View\Type\Configurable">
        <plugin name="Wk_afterGetJsonConfig"
          type="Webkul\ExtraContentInFotorama\Plugin\ConfigurableProduct\Block\ConfigurableAfterGetJsonConfigPlugin"
          sortOrder="50" />
    	</type>
    </config>

    2. Create ConfigurableAfterGetJsonConfigPlugin.php file inside the <magento-root-dir>/app/code/Webkul/ExtraContentInFotorama/Plugin/ConfigurableProduct/Block/ directory.

    Searching for an experienced
    Magento 2 Company ?
    Find out More
    <?php
    /**
     * Webkul Software.
     *
     * @category  Webkul
     * @package   Webkul_ExtraContentInFotorama
     * @author    Webkul Software Private Limited
     * @copyright Webkul Software Private Limited (https://webkul.com)
     * @license   https://store.webkul.com/license.html
     */
    namespace Webkul\ExtraContentInFotorama\Plugin\ConfigurableProduct\Block;
    
    class ConfigurableAfterGetJsonConfigPlugin
    {
        /**
         * @var \Magento\Framework\App\RequestInterface
         */
        protected $request;
    
        /**
         * @var \Magento\Framework\Json\Helper\Data
         */
        protected $jsonHelper;
    
        /**
         * Initialize dependencies
         *
         * @param \Magento\Framework\Json\Helper\Data $jsonHelper
         * @param \Magento\Framework\App\RequestInterface $request
         * @return void
         */
        public function __construct(
            \Magento\Framework\Json\Helper\Data $jsonHelper,
            \Magento\Framework\App\RequestInterface $request
        ) {
            $this->request    = $request;
            $this->jsonHelper = $jsonHelper;
        }
    
        /**
         * Composes configuration for js
         *
         * @param \Magento\ConfigurableProduct\Block\Product\View\Type\Configurable $subject
         * @param string $resultJson
         * @return string
         */
        public function afterGetJsonConfig(
            \Magento\ConfigurableProduct\Block\Product\View\Type\Configurable $subject,
            $resultJson
        ) {
            //Check if request is made from product page//
            if (strtolower($this->request->getFullActionName()) == "catalog_product_view") {
                $result = $this->jsonHelper->jsonDecode($resultJson);
    
                /////Set Extra Configuration Data/////
                $customConfig = [];
                
                $customConfig["linkUrl"] = "https://webkul.com/blog/design-patterns-in-magento-2/";
                $customConfig["thumbnailImage"] = "Provide a thumbnail Image URL";
                $customConfig["linkContent"] =  "Design Patterns in Magento 2";
    
                //set super attribute id, on which selection you want to change the content//
                $customConfig["defaultVariantAttribute"] = 138; 
    
                $result["customConfig"] = $customConfig;
    
                $resultJson = $this->jsonHelper->jsonEncode($result);
    
                /////////
            }
           
            return $resultJson;
        }
    }

    3. Create a requires-config.js file to mention the mixin file for the swatch-renderer.js file inside the <magento-root-dir.>/app/code/Webkul/ExtraContentInFotorama/view/frontend/ directory.

    /**
     * Webkul Software.
     *
     * @category  Webkul
     * @package   Webkul_ExtraContentInFotorama
     * @author    Webkul Software Private Limited
     * @copyright Webkul Software Private Limited (https://webkul.com)
     * @license   https://store.webkul.com/license.html
     */
    var config = {
        config: {
            mixins: {
                'Magento_Swatches/js/swatch-renderer' : {'Webkul_ExtraContentInFotorama/js/swatch-renderer':true}
            }
        }
    };

    4. Create a swatch-renderer.js file inside the <magento-root-dir.>/app/code/Webkul/ExtraContentInFotorama/view/frontend/web/js/ directory.

    /**
     * Webkul Software.
     *
     * @category  Webkul
     * @package   Webkul_ExtraContentInFotorama
     * @author    Webkul Software Private Limited
     * @copyright Webkul Software Private Limited (https://webkul.com)
     * @license   https://store.webkul.com/license.html
     */
    define([
        'jquery',
        'underscore',
        'mage/translate'
    ], function ($, _, $t) {
        'use strict';
    
        return function (SwatchRenderer) {
            
            $.widget('mage.SwatchRenderer', SwatchRenderer, {
                /**
                 * Update [gallery-placeholder] or [product-image-photo]
                 * @param {Array} images
                 * @param {jQuery} context
                 * @param {Boolean} isInProductView
                 */
                updateBaseImage: function (images, context, isInProductView) {
                    var self = this.options.jsonConfig.customConfig; //Load Custom Content Config.
    
                    ///Check if linkUrl is empty//
                    if (self.linkUrl == "" || self.linkUrl == null) {
                        return this._super(images, context, isInProductView);
                    }
                    /////
    
                    var justAnImage = images[0],
                        initialImages = this.options.mediaGalleryInitial,
                        imagesToUpdate,
                        gallery = context.find(this.options.mediaGallerySelector).data('gallery'),
                        isInitial;
    
                    if (isInProductView) {
                        if (_.isUndefined(gallery)) {
                            context.find(this.options.mediaGallerySelector).on('gallery:loaded', function () {
                                this.updateBaseImage(images, context, isInProductView);
                            }.bind(this));
    
                            return;
                        }
    
                        imagesToUpdate = images.length ? this._setImageType($.extend(true, [], images)) : [];
                        isInitial = _.isEqual(imagesToUpdate, initialImages);
    
                        if (this.options.gallerySwitchStrategy === 'prepend' && !isInitial) {
                            //Remove Additional Extra Content
                            initialImages = this._removeExtraContentfromArray(initialImages);
                            //////////////
                            imagesToUpdate = imagesToUpdate.concat(initialImages);
                        }
    
                        imagesToUpdate = this._setImageIndex(imagesToUpdate);
    
                        gallery.updateData(imagesToUpdate);
                        this._addFotoramaVideoEvents(isInitial);
                    } else if (justAnImage && justAnImage.img) {
                        context.find('.product-image-photo').attr('src', justAnImage.img);
                    }
                },
    
                /**
                 * Callback which fired after gallery gets initialized.
                 *
                 * @param {HTMLElement} element - DOM element associated with a gallery.
                 */
                _onGalleryLoaded: function (element) {
                    var galleryObject = element.data('gallery');
    
                    ////////////
                    var currImgs = galleryObject.returnCurrentImages();
                
                    //Load Extra Content//
                    this._loadExtraContent();
    
                    //Push Extra Content in images gallery
                    var self = this.options.jsonConfig.customConfig;
                    var modelThumbnailImg = self.thumbnailImage;
                    
                    this._pushExtraContent(currImgs, modelThumbnailImg);
                    //////////////////
    
                    this.options.mediaGalleryInitial = currImgs;
    
                    ///Update Current Images in Media Gallery///
                    galleryObject.updateData(currImgs);
                },
    
                /**
                 * Load media gallery using ajax or json config.
                 *
                 * @private
                 */
                _loadMedia: function () {
                    var self = this.options.jsonConfig.customConfig; //Get Custom Config.
    
                    var $main = this.inProductList ?
                            this.element.parents('.product-item-info') :
                            this.element.parents('.column.main'),
                        images;
    
                    if (this.options.useAjax) {
                        this._debouncedLoadProductMedia();
                    }  else {
                        images = this.options.jsonConfig.images[this.getProduct()];
    
                        if (!images) {
                            images = this.options.mediaGalleryInitial;
                        }
    
                        ////////////Load Extra Content/////
                        this._loadExtraContent();
                        //Remove Extra Content from image array
                        images = this._removeExtraContentfromArray(images);
    
                        var self = this.options.jsonConfig.customConfig;
                        var modelThumbnailImg = self.thumbnailImage;
                        
                        //Push Extra Content from image array
                        this._pushExtraContent(images, modelThumbnailImg);
                        //////////////////
                
                        this.updateBaseImage(this._sortImages(images), $main, !this.inProductList);
                    }
                },
    
    
                /**
                 * Delete Additional Extra Content from array
                 * 
                 * @param {Array} imagesArray
                 * @returns {Array}
                 * @private
                 */
                _removeExtraContentfromArray: function(imagesArray) {
                    imagesArray = imagesArray.filter(element => element.type !== "ExtraContent");
                    return imagesArray;
                },
    
                /**
                 * Push Extra Content in array
                 * 
                 * @param {Array} fotoramaContentArray
                 * @param {string} modelThumbnailImg
                 * @private
                 */
                _pushExtraContent: function(fotoramaContentArray, modelThumbnailImg) {
                    var self = this.options.jsonConfig.customConfig;
    
                    if (typeof fotoramaContentArray != "undefined" && 
                        (self.linkUrl != "" || self.linkUrl != null)) {
                            fotoramaContentArray.unshift({
                            thumb: modelThumbnailImg,
                            src: self.linkUrl,
                            type: 'ExtraContent',
                            caption: self.linkContent,
                            isMain: "true",
                            position: 0
                        });
                    }
                },
    
                /**
                 * Get Selected Variant Value
                 * 
                 * @return {string}
                 * @private
                 */
                _getSelectedVariantValue: function() {
                    var self = this.options.jsonConfig.customConfig;
                    var optionTextVal = "";
                    var optionTextArr = []; 
                    var selectedText = "";
                    var selectedVal = "";
                    var selectedSwatchAttrId = 0;
                    var defaultVariantAttribute = self.defaultVariantAttribute;
                
                    if ($('.product-options-wrapper select[id^="attribute"]').find().length) {
                        selectedText = $('.product-options-wrapper select[id^="attribute"] option:selected').text();
                    } else {
                        if ($('.swatch-attribute-options .swatch-option[aria-checked="true"]').length) {
                            var swatchId = "";
                            var idParts = [];
                            $('.swatch-attribute-options .swatch-option[aria-checked="true"]').each(function() {
                                swatchId = $(this).attr("id");
                                idParts = swatchId.split('-');
    
                                for (let index=0; index<idParts.length; index++) {
                                    if ($.isNumeric(idParts[index])) {
                                        selectedSwatchAttrId = idParts[index];
                                        break;
                                    }
                                }
                                if (parseInt(defaultVariantAttribute) == selectedSwatchAttrId) {
                                    selectedText = $(this).attr("data-option-label");
                                }
                            });
                        }
                    }
    
                    /////Get Selected Variant Value/////
                    selectedVal = $('.product-options-wrapper select[id^="attribute"] option:selected').val();
                    if (selectedVal == "" && $('.product-options-wrapper select[id^="attribute"]').find().length) {
                        selectedText = $('.product-options-wrapper select[id^="attribute"] option:eq(1)').text();
                    } else {
                        selectedVal = selectedText;
                    }
                    /////
    
                    if ($('.product-options-wrapper select[id^="attribute"]').find().length) {
                        if (selectedText.indexOf('+') == -1) {
                            optionTextVal = selectedText;
                        } else {
                            optionTextArr =  selectedText.split('+');
                            optionTextVal = $.trim(optionTextArr[0]);
                        }    
                    } else {
                        optionTextVal = selectedVal;
                    }
                  
                    return optionTextVal;
                },
    
    
                /**
                 * Event for swatch options
                 *
                 * @param {Object} $this
                 * @param {Object} $widget
                 * @private
                 */
                _OnClick: function ($this, $widget) {
                    var self = this.options.jsonConfig.customConfig;
                    var wkExtraContentDiv = $("#wkExtraContent");
                    var mainVariantAttributeId = self.defaultVariantAttribute;
    
                    var swatchId = $this.attr("id");
                    var idParts = swatchId.split('-');
                    var selectedSwatchAttrId = 0;
    
                    for (let index=0; index<idParts.length; index++) {
                        if ($.isNumeric(idParts[index])) {
                            selectedSwatchAttrId = idParts[index];
                            break;
                        }
                    }
                    
                    if (typeof wkExtraContentDiv == "object" 
                        && selectedSwatchAttrId == parseInt(mainVariantAttributeId)) {
                            var label = $this.attr("data-option-label");
                            var bgColor = "#86FA50";
    
                            switch (label) {
                                case 'Gold': bgColor = "#DFD906"; break;
                                case 'Diamond': bgColor = "#AFF3F2"; break;
                            }
    
                            setTimeout(function(){
                                $("#wkExtraContent").css("background-color", bgColor);
                            }, 300);
                            
                    }
    
                    this._super($this, $widget);
                },
    
                /**
                 * Load Extra Content
                 * 
                 * @private
                 */
                _loadExtraContent: function() {
                    var thisJs = this;
                    var self = this.options.jsonConfig.customConfig;
                    var divFotorama = $('div.gallery-placeholder > div.fotorama');
    
                    if (self.linkUrl == "") {
                        return;
                    }
                    var variantText = "None";
                     
                    //Get Selected Variant Value
                    variantText = thisJs._getSelectedVariantValue();
    
                    divFotorama.on('fotorama:load', function fotorama_onLoad(e, fotorama, extra) {
    
                        if (extra.frame.type === 'ExtraContent' && extra.frame.src != "") {
                            var extraContentHtml = '';
                           
                            extraContentHtml += '<div id="wkExtraContent" style="background-color:#86FA50">';
                            extraContentHtml += '<h1 style="margin-top:250px">'+'Extra Content'+'</h1>';
                            ///Show Variant Text///
                            if (variantText != '') {
                                extraContentHtml += '<p style="font-size:20px">Selected Variant:</p>';
                                extraContentHtml += '<p style="font-size:18px">'+variantText+'</p><br/>';
                            }
                            /////////////////
                            extraContentHtml += '<a href="'+self.linkUrl+'" style="font-size:18px">'
                            extraContentHtml += self.linkContent+'</a>';
                            extraContentHtml += '</div>';
    
                            extra.frame.$stageFrame.html(extraContentHtml);
                        }
                    });
                }
    
            });
    
            return $.mage.SwatchRenderer;
        };
    });

    5. After adding the above code to your module. Deploy the code and see the result in the text swatch case. Refer to the below images for the result.

    TextSwatchAttribute
    TextSwatchResult

    6. See the result in the visual swatch case. Refer to the below images for the result.

    VisualSwatchAttribute
    VisualSwatchResult

    Note: Remember to add <sequence> in the module.xml file to load the Magento_Swatches module before your custom module.

    You may also check the Magento 2 Advanced Media Manager extension to edit the images present in the media gallery and add watermarks, filter, rotate, resize, and more.

    . . .

    Leave a Comment

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


    Be the first to comment.

    Back to Top

    Message Sent!

    If you have more details or questions, you can reply to the received confirmation email.

    Back to Home