Magento2 | PWA | GraphQL

Enabling Product Selection and Displaying Product Grid in Magento 2 System Configuration


The Dynamic Product module enables you to select specific products directly from the product grid within the system configuration tab. These selections are then saved in the core_config_data table, allowing you to access and display them wherever needed.

You can find the complete module on GitHub at Magelearn_DynamicProduct

Or Check the below images for a better understanding of the functionality of this module.






Let's start it by creating a custom extension. 

Create a folder inside app/code/Magelearn/DynamicProduct

Add registration.php file in it:

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Magelearn_DynamicProduct',
    __DIR__
);

Add composer.json file in it:

{
    "name": "magelearn/magento2-module-dynamic-product",
    "description": "The Dynamic Product module enables you to select specific products directly from the product grid within the system configuration tab. These selections are then saved in the `core_config_data` table, allowing you to access and display them wherever needed.",
    "type": "magento2-module",
    "license": "Apache-2.0",
    "authors": [
        {
            "name": "Vijay Rami",
            "email": "vijaymrami@gmail.com"
        }
    ],
    "minimum-stability": "dev",
    "prefer-stable": true,
    "version":"1.0",
    "require": {
        "php": ">=8.0.0"
    },
    "repositories": [
    {
        "type": "package",
        "package": {
            "name": "magelearn/magento2-module-dynamic-product",
            "version": "1.0",
            "source": {
                "url": "https://github.com/vijayrami/Magelearn_DynamicProduct.git",
                "type": "git",
                "reference": "main"
            }
        }
    }
    ],
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Magelearn\\DynamicProduct\\": ""
        }
    }
}

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_DynamicProduct" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Backend"/>
            <module name="Magento_Config"/>
            <module name="Magento_Cms"/>
        </sequence>
    </module>
</config>

Add 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">
                            <resource id="Magelearn_DynamicProduct::config" title="Magelearn DynamicProduct Grid Section" sortOrder="50" />
                        </resource>
                    </resource>
                </resource>
            </resource>
        </resources>
    </acl>
</config>

Now to give the CMS Page list options at System configuration at admin,

we will add 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>
        <tab id="magelearn" translate="label" sortOrder="10">
            <label>Magelearn Modules</label>
        </tab>
        <section id="dynamic_list" translate="label" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
            <class>separator-top</class>
            <label>Select Products From Grid</label>
            <tab>magelearn</tab>
            <resource>Magelearn_DynamicProduct::config</resource>
            <group id="selection" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>General Configuration</label>
                <field id="select_product" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Select Product</label>
                    <frontend_model>Magelearn\DynamicProduct\Block\Adminhtml\System\Config\Product</frontend_model>
                </field>
            </group>
        </section>
    </system>
</config>

Now as per highlighted code above, we will add
Block/Adminhtml/System/Config/Product.php file.

<?php
declare(strict_types=1);

namespace Magelearn\DynamicProduct\Block\Adminhtml\System\Config;

use Magento\Backend\Block\Template\Context;
use Magento\Framework\Data\Form\Element\AbstractElement;
use Magento\Config\Block\System\Config\Form\Field;
use Magento\Backend\Block\Widget\Grid\Extended;

class Product extends Field
{
    protected $_template = 'Magelearn_DynamicProduct::system/config/product.phtml';
    
    public function __construct(
        Context $context,
        array $data = []
        ) {
            parent::__construct($context, $data);
    }
    
    protected function _getElementHtml(AbstractElement $element)
    {
        $this->setElement($element);
        return $this->_toHtml();
    }
    
    public function getProductGridUrl()
    {
        return $this->getUrl('dynamicproduct/product/grid', [
            'element_id' => $this->getElement()->getHtmlId()
        ]);
    }
    
    public function getFormKey()
    {
        return $this->formKey->getFormKey();
    }
}

Now as per highlighted code above, we will add our template file and route file.

Add view/adminhtml/templates/system/config/product.phtml file.

<?php
/** @var \Magelearn\DynamicProduct\Block\Adminhtml\System\Config\Product $block */
$element = $block->getElement();
$fieldId = $element->getHtmlId();
?>
<div class="admin__field">
    <div class="admin__field-control">
        <input type="text" 
               id="<?= $block->escapeHtmlAttr($fieldId) ?>"
               name="<?= $block->escapeHtmlAttr($element->getName()) ?>"
               value="<?= $block->escapeHtmlAttr($element->getValue()) ?>"
               class="input-text admin__control-text"
               readonly="readonly" />
        <button type="button" 
                class="action-default scalable" 
                id="<?= $block->escapeHtmlAttr($fieldId) ?>_button">
            <span><?= $block->escapeHtml(__('Select Product')) ?></span>
        </button>
        <div id="<?= $block->escapeHtmlAttr($fieldId) ?>_modal" class="product-chooser-modal" style="display:none;"></div>
    </div>
</div>

<script>
    require([
        'jquery',
        'Magento_Ui/js/modal/modal',
        'mage/url',
        'productChooser'
    ], function($, modal, urlBuilder, productChooser) {
        'use strict';
        
        productChooser({
            fieldId: '<?= $block->escapeJs($fieldId) ?>',
            gridUrl: '<?= $block->escapeJs($block->getProductGridUrl()) ?>',
            formKey: '<?= $block->escapeJs($block->getFormKey()) ?>'
        });
    });
</script>

Also add etc/adminhtml/routes.xml file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="admin">
        <route id="dynamicproduct" frontName="dynamicproduct">
            <module name="Magelearn_DynamicProduct" />
        </route>
    </router>
</config>

Now add Controller file at Controller/Adminhtml/Product/Grid.php

<?php
declare(strict_types=1);

namespace Magelearn\DynamicProduct\Controller\Adminhtml\Product;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\View\Result\LayoutFactory;

class Grid extends Action
{
    protected $resultLayoutFactory;

    public function __construct(
        Context $context,
        LayoutFactory $resultLayoutFactory
    ) {
        parent::__construct($context);
        $this->resultLayoutFactory = $resultLayoutFactory;
    }

    public function execute()
    {
        $resultLayout = $this->resultLayoutFactory->create();
        $resultLayout->getLayout()->getBlock('product.grid');
        return $resultLayout;
    }
}

Also add layout file at
app/code/Magelearn/DynamicProduct/view/adminhtml/layout/dynamicproduct_product_grid.xml

<?xml version="1.0"?>
<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd">
    <container name="root" label="Root">
        <block class="Magelearn\DynamicProduct\Block\Adminhtml\Product\Grid" name="product.grid"/>
    </container>
</layout>

Now as per highlighted code above, add our block class at
app/code/Magelearn/DynamicProduct/Block/Adminhtml/Product/Grid.php

<?php
declare(strict_types=1);

namespace Magelearn\DynamicProduct\Block\Adminhtml\Product;

use Magento\Backend\Block\Widget\Grid\Extended;
use Magento\Backend\Block\Template\Context;
use Magento\Backend\Helper\Data;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\Catalog\Model\Product\Visibility;
use Magento\Catalog\Model\Product\Attribute\Source\Status;

class Grid extends Extended
{
    protected $productCollectionFactory;
    protected $visibility;
    protected $status;
    
    public function __construct(
        Context $context,
        Data $backendHelper,
        CollectionFactory $productCollectionFactory,
        Visibility $visibility,
        Status $status,
        array $data = []
        ) {
            $this->productCollectionFactory = $productCollectionFactory;
            $this->visibility = $visibility;
            $this->status = $status;
            parent::__construct($context, $backendHelper, $data);
    }
    
    protected function _construct()
    {
        parent::_construct();
        $this->setId('productGrid');
        $this->setDefaultSort('entity_id');
        $this->setDefaultDir('DESC');
        $this->setUseAjax(true);
        $this->setSaveParametersInSession(false);
        // Set this to false to show radio buttons instead of checkboxes
        $this->setMassactionBlockName('Magento\Backend\Block\Widget\Grid\Massaction');
    }
    
    protected function _prepareCollection()
    {
        $collection = $this->productCollectionFactory->create();
        $collection->addAttributeToSelect(['name', 'sku', 'price', 'status'])
        ->addAttributeToFilter('status', ['in' => $this->status->getVisibleStatusIds()])
        ->addAttributeToFilter('visibility', ['in' => $this->visibility->getVisibleInSiteIds()]);
        
        $this->setCollection($collection);
        return parent::_prepareCollection();
    }
    
    protected function _prepareColumns()
    {
        // Add checkbox column before other columns
        $this->addColumn('in_products', [
            'header_css_class' => 'a-center',
            'type' => 'checkbox', // Use 'radio' for single selection, 'checkbox' for multiple
            'name' => 'in_products',
            'values' => $this->_getSelectedProducts(),
            'align' => 'center',
            'index' => 'entity_id',
            'header' => false,
            'renderer' => 'Magelearn\DynamicProduct\Block\Adminhtml\Grid\Renderer\Checkbox'
            //'html_name' => 'product_id' // For Radio Button only
        ]);
        
        $this->addColumn('entity_id', [
            'header' => __('ID'),
            'type' => 'number',
            'index' => 'entity_id',
            'header_css_class' => 'col-id',
            'column_css_class' => 'col-id'
        ]);
        
        $this->addColumn('name', [
            'header' => __('Name'),
            'index' => 'name'
        ]);
        
        $this->addColumn('sku', [
            'header' => __('SKU'),
            'index' => 'sku'
        ]);
        
        $this->addColumn('price', [
            'header' => __('Price'),
            'type' => 'currency',
            'currency_code' => (string)$this->_scopeConfig->getValue(
                \Magento\Directory\Model\Currency::XML_PATH_CURRENCY_BASE,
                \Magento\Store\Model\ScopeInterface::SCOPE_STORE
                ),
            'index' => 'price'
        ]);
        
        return parent::_prepareColumns();
    }
    
    protected function _getSelectedProducts()
    {
        $selected = $this->getRequest()->getParam('selected', []);
        return is_array($selected) ? $selected : [];
    }
    
    public function getGridUrl()
    {
        return $this->getUrl('dynamicproduct/product/grid', ['_current' => true]);
    }
    
    // This function is important for row click handling
    public function getRowUrl($row)
    {
        return '#';
    }
}

Now as per the highlighted code above, to render the checkboxes add our renderer class at
app/code/Magelearn/DynamicProduct/Block/Adminhtml/Grid/Renderer/Checkbox.php

<?php
declare(strict_types=1);

namespace Magelearn\DynamicProduct\Block\Adminhtml\Grid\Renderer;

use Magento\Framework\DataObject;

class Checkbox extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\Checkbox
{
    public function render(DataObject $row)
    {
        $entityId = $row->getData($this->getColumn()->getIndex());
        $htmlName = 'product_id'; // Define the custom HTML name here

        return '<input type="checkbox" name="' . $htmlName . '[]" value="' . $entityId . '" class="checkbox" />';
    }
}

Now as per the highlighted code in templates view/adminhtml/templates/system/config/product.phtml file,

We will add our JS file.

For that add file at view/adminhtml/requirejs-config.js

var config = {
    map: {
        '*': {
            productChooser: 'Magelearn_DynamicProduct/js/product-chooser'
        }
    }
};

Now as per the highlighted code above add the file at view/adminhtml/web/js/product-chooser.js

define([
    'jquery',
    'Magento_Ui/js/modal/modal',
    'mage/translate'
], function($, modal, $t) {
    'use strict';
    
    return function(config) {
        var fieldId = config.fieldId;
        var buttonId = fieldId + '_button';
        var modalId = fieldId + '_modal';
        
        // Initialize modal
        var modalElement = $('#' + modalId);
        var modalOptions = {
            type: 'slide',
            responsive: true,
            innerScroll: true,
            modalClass: 'product-chooser-modal-container',
            title: $t('Choose Product'),
            buttons: [{
                text: $t('Select'),
                class: 'action-primary',
                click: function() {
                    var selected = modalElement.find('input[name="product_id[]"]:checked');
                    if (selected.length) {
                        var productIds = selected.map(function() {
                            return $(this).val();
                        }).get();
                        var productName = selected.closest('tr').find('td:nth-child(3)').text().trim();
                        console.log('Selected product name:', productName);
                        $('#' + fieldId).val(productIds.join(','));
                        modalElement.modal('closeModal');
                    } else {
                        alert($t('Please select a product.'));
                    }
                }
            }, {
                text: $t('Cancel'),
                class: 'action-secondary',
                click: function() {
                    modalElement.modal('closeModal');
                }
            }]
        };
        
        modalElement.modal(modalOptions);
        
        // Handle button click
        $('#' + buttonId).on('click', function(e) {
            e.preventDefault();
            
            $.ajax({
                url: config.gridUrl,
                type: 'POST',
                dataType: 'html',
                data: {
                    form_key: config.formKey
                },
                showLoader: true,
                success: function(data) {
                    modalElement.html(data).trigger('contentUpdated');
                    modalElement.modal('openModal');
                    
                    // Add click handler for rows
                    modalElement.on('click', 'tr.data-row', function(e) {
                        // Don't trigger if clicking on the checkbox button itself
                        if (!$(e.target).is('input[type="checkbox"]')) {
                            var checkbox = $(this).find('input[type="checkbox"]');
                            checkbox.prop('checked', !checkbox.prop('checked'));
                        }
                    });
                },
                error: function(xhr, status, error) {
                    console.error('Error loading product grid:', error);
                }
            });
        });
    };
});

Now to display product chooser modal window properly, we will add our CSS file at
view/adminhtml/web/css/source/_module.less

0 Comments On "Enabling Product Selection and Displaying Product Grid in Magento 2 System Configuration"

Back To Top