How to Add Custom Grid into a Custom Tab in Product Edit Page in Magento 2 Admin

If you have been on this blog before, you probably read the post Create Custom Tab under Product Edit Section. In that post, you have seen how we have added an HTML to custom tab without creating a product attribute. After reading that post many readers have requested to write a post on How to Add Custom Grid into a Custom Tab in Product Edit Page in Magento 2 Admin.

Today, we will create a custom tab in product edit page and in that tab we will display a custom grid from another module.

Add custom grid into a Custom Tab in Product edit page
Custom Grid In Product Edit Page

Before we start Module development

  • I assume that you have already created custom grid module. We do not cover how to create a custom grid module in Magento 2 admin in this post.
  • Your Magento 2 cache is disabled.

Getting Started: Development

Step 1: Create module.xml file under the app/code/Codextblog/Customgridtab/etc directory

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd">
    <module name="Codextblog_Customgridtab" setup_version="1.0.0"></module>
</config>

Step 2: Create registration.php file under the app/code/Codextblog/Customgridtab directory

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

Step 3: Declare modifier in app/code/Codextblog/Customgridtab/etc/adminhtml/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">
   <virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool">
        <arguments>
            <argument name="modifiers" xsi:type="array">
                <item name="custom-tab-grid-with-content" xsi:type="array">
                    <item name="class" xsi:type="string">Codextblog\Customgridtab\Ui\DataProvider\Product\Form\Modifier\CustomTab</item>
                    <item name="sortOrder" xsi:type="number">10</item>
                </item>
            </argument>
        </arguments>
    </virtualType>
</config>

In this file Under the <item> tag, we have passed a class file. This file will create a custom tab in product edit page.

Step 4: Create modifier class CustomTab under app/code/Codextblog/Customgridtab/Ui/Dataprovider/Product/Form/Modifier directory

<?php namespace Codextblog\Customgridtab\Ui\DataProvider\Product\Form\Modifier; use Magento\Catalog\Model\Locator\LocatorInterface; use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier; use Magento\Framework\Stdlib\ArrayManager; use Magento\Framework\UrlInterface; use Magento\Ui\Component\Container; use Magento\Ui\Component\Form\Fieldset; class CustomTab extends AbstractModifier { const SAMPLE_FIELDSET_NAME = 'custom_grid_fieldset'; const SAMPLE_FIELD_NAME = 'custom_grid'; protected $_backendUrl; protected $_productloader; protected $_modelCustomgridFactory; /** * @var \Magento\Catalog\Model\Locator\LocatorInterface */ protected $locator; /** * @var ArrayManager */ protected $arrayManager; /** * @var UrlInterface */ protected $urlBuilder; /** * @var array */ protected $meta = []; /** * @param LocatorInterface $locator * @param ArrayManager $arrayManager * @param UrlInterface $urlBuilder */ public function __construct( LocatorInterface $locator, ArrayManager $arrayManager, UrlInterface $urlBuilder, \Magento\Catalog\Model\ProductFactory $_productloader, \Magento\Backend\Model\UrlInterface $backendUrl ) { $this->locator = $locator;
        $this->arrayManager = $arrayManager;
        $this->urlBuilder = $urlBuilder;
        $this->_productloader = $_productloader;
        $this->_backendUrl = $backendUrl;
    }
 
    public function modifyData(array $data)
    {
        return $data;
    }
 
    public function modifyMeta(array $meta)
    {
        $this->meta = $meta;
        $this->addCustomTab();
 
        return $this->meta;
    }
 
    protected function addCustomTab()
    {
        $this->meta = array_merge_recursive(
            $this->meta,
            [
                static::SAMPLE_FIELDSET_NAME => $this->getTabConfig(),
            ]
        );
    }
 
    protected function getTabConfig()
    {
    return [
        'arguments' => [
            'data' => [
                'config' => [
                    'label' => __('Custom Grid Tab'),
                    'componentType' => Fieldset::NAME,
                    'dataScope' => '',
                    'provider' => static::FORM_NAME . '.product_form_data_source',
                    'ns' => static::FORM_NAME,
                    'collapsible' => true,
                ],
            ],
        ],
        'children' => [
            static::SAMPLE_FIELD_NAME => [
                'arguments' => [
                    'data' => [
                        'config' => [
                            'autoRender' => true,
                            'componentType' => 'insertListing',
                            'dataScope' => 'custom_grid_listing',
                            'externalProvider' => 'custom_grid_listing.custom_grid_listing_data_source',
                            'selectionsProvider' => 'custom_grid_listing.custom_grid_listing.product_columns.ids',
                            'ns' => 'custom_grid_listing',
                            'render_url' => $this->urlBuilder->getUrl('mui/index/render'),
                            'realTimeLink' => false,
                            'behaviourType' => 'simple',
                            'externalFilterMode' => true,
                            'imports' => [
                                'productId' => '${ $.provider }:data.product.current_product_id'
                            ],
                            'exports' => [
                                'productId' => '${ $.externalProvider }:params.current_product_id'
                            ],

                        ],
                    ],
                ],
                'children' => [],
            ],
        ],
    ];
    }

}

This file is responsible for creating a custom tab. Function getTabConfig return an array that contains all the information which are needed to create a custom tab. In this function in line no 106 we have defined node ‘componentType’ with value as ‘insertlisting’ that means our tab will display the listing. In the previous post, we have defined the ‘componentType’ as a field because we need only filed to display but here we need to display grid so we have passed a value as ‘insertlisting’.

In line no 107, we have passed node ‘dataScope’ with value as ‘custom_grid_listing’ indicates the name of our ui_component element. Next we will define ui_component file custom_grid_listing.xml

Step 5: Create ui component xml file custom_grid_listing.xml under app/code/Codextblog/Customgridtab/view/adminhtml/ui_component directory

<?xml version="1.0" encoding="UTF-8"?>
<!-- /** * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -->
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">custom_grid_listing.custom_grid_listing_data_source</item>
            <item name="deps" xsi:type="string">custom_grid_listing.custom_grid_listing_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">custom_grid_columns</item>
    </argument>
    <dataSource name="custom_grid_listing_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Codextblog\Customgridtab\Ui\DataProvider\Product\CustomDataProvider</argument>
            <argument name="name" xsi:type="string">custom_grid_listing_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">id</argument>
            <argument name="requestFieldName" xsi:type="string">id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
                    <item name="update_url" xsi:type="url" path="mui/index/render"/>
                    <item name="storageConfig" xsi:type="array">
                        <item name="cacheRequests" xsi:type="boolean">false</item>
                    </item>
                </item>
            </argument>
        </argument>
    </dataSource>
    <listingToolbar name="listing_top">
        <filters name="listing_filters">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="statefull" xsi:type="array">
                        <item name="applied" xsi:type="boolean">false</item>
                    </item>
                    <item name="params" xsi:type="array">
                        <item name="filters_modifier" xsi:type="array"/>
                    </item>
                </item>
            </argument>
        </filters>
        <paging name="listing_paging"/>
    </listingToolbar>
    <columns name="custom_grid_columns" class="Magento\Ui\Component\Listing\Columns">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="childDefaults" xsi:type="array">
                    <item name="fieldAction" xsi:type="array">
                        <item name="provider" xsi:type="string">customGrid</item>
                        <item name="target" xsi:type="string">selectData</item>
                        <item name="params" xsi:type="array">
                            <item name="0" xsi:type="string">${ $.$data.rowIndex }</item>
                        </item>
                    </item>
                </item>
            </item>
        </argument>
        <column name="id">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">textRange</item>
                    <item name="sorting" xsi:type="string">asc</item>
                    <item name="label" xsi:type="string" translate="true">ID</item>
                    <item name="sortOrder" xsi:type="number">0</item>
                </item>
            </argument>
        </column>
        <column name="title">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Title</item>
                    <item name="sortOrder" xsi:type="number">10</item>
                    <item name="truncate" xsi:type="number">50</item>
                    <item name="nl2br" xsi:type="boolean">true</item>
                    <item name="escape" xsi:type="boolean">true</item>
                </item>
            </argument>
        </column>
        <column name="is_active" class="Codextblog\Customgridtab\Ui\Component\Listing\Columns\Active">
            <argument name="data" xsi:type="array">
                <item name="options" xsi:type="object">Codextblog\Customgridtab\Ui\Component\Listing\Columns\Active</item>
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">select</item>
                    <item name="dataType" xsi:type="string">select</item>
                    <item name="label" xsi:type="string" translate="true">Active</item>
                    <item name="sortOrder" xsi:type="number">20</item>
                </item>
            </argument>
        </column>
        <column name="date_created" class="Magento\Ui\Component\Listing\Columns\Date">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">dateRange</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/date</item>
                    <item name="dataType" xsi:type="string">date</item>
                    <item name="label" xsi:type="string" translate="true">Created At</item>
                    <item name="sortOrder" xsi:type="number">30</item>
                </item>
            </argument>
        </column>
    </columns>
</listing>

Let’s understand this file. The very important tag in this file is <dataSource> that contains the following arguments

  • class: define the DataProvider class Codextblog\Customgridtab\Ui\DataProvider\Product\CustomDataProvider which provide the collection for rendering the grid. We will create this class in next step.
  • name: define unique name
  • primaryFieldName: define the primary field of a database table which we use in our collection. In our case it is ‘id’.
  • requestFieldName: same as primaryFieldName
  • data: define some of the Magento 2 inbuilt data parameter to render the grid

Here other important tags are <listingtoolbar> and <columns> which are responsible for render toolbar (like pagination, filter) and display defined columns in the grid respectively. In the <columns> we have defined id, title, is_active and date_created so our grid will display only these four columns in the grid. We can add as many columns as we want. We just need to defined columns name with type under <columns> tag.

In one of the columns, we have defined is_active field. This field will render the drop-down with options. To render those options we have provided the class Codextblog\Customgridtab\Ui\Component\Listing\Columns\Active

step 6: Let’s create this class Active.php under app/code/Codextblog/Customgridtab/Ui/Component/Listing/Columns directory

<?php /** * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Codextblog\Customgridtab\Ui\Component\Listing\Columns; use Magento\Framework\Data\OptionSourceInterface; use Magento\Framework\View\Element\UiComponentFactory; use Magento\Ui\Component\Listing\Columns\Column; use Magento\Framework\View\Element\UiComponent\ContextInterface; /** * Class Active */ class Active extends Column implements OptionSourceInterface { /** * @param ContextInterface $context * @param UiComponentFactory $uiComponentFactory * @param StatusSource $source * @param array $components * @param array $data */ public function __construct( ContextInterface $context, UiComponentFactory $uiComponentFactory, array $components = [], array $data = [] ) { parent::__construct($context, $uiComponentFactory, $components, $data); } /** * {@inheritdoc} */ public function prepareDataSource(array $dataSource) { $dataSource = parent::prepareDataSource($dataSource); $options = [ 1 => __('Yes'),
        2 => __('No')

    ];

        if (empty($dataSource['data']['items'])) {
            return $dataSource;
        }

        foreach ($dataSource['data']['items'] as &$item) {
            if (isset($options[$item['is_active']])) {
                $item['is_active'] = $options[$item['is_active']];
            }
        }

        return $dataSource;
    }

    /**
     * {@inheritdoc}
     */
    public function toOptionArray()
    {
        $result = [];
        $result[] = ['value' => 1, 'label' => 'Yes'];
        $result[] = ['value' => 2, 'label' => 'No'];

        return $result;
    }


}

Step 7: Create Dataprovider class CustomDataProvider.php under app/code/Codextblog/Customgridtab/Ui/DataProvider/Product directory

<?php /** * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Codextblog\Customgridtab\Ui\DataProvider\Product; use Magento\Framework\App\RequestInterface; use Magento\Ui\DataProvider\AbstractDataProvider; use Codextblog\Customgrid\Model\ResourceModel\Post\CollectionFactory; use Codextblog\Customgrid\Model\ResourceModel\Post\Collection; use Codextblog\Customgrid\Model\Post; /** * Class CustomDataProvider * * @method Collection getCollection */ class CustomDataProvider extends AbstractDataProvider { /** * @var CollectionFactory */ protected $collectionFactory; /** * @var RequestInterface */ protected $request; /** * @param string $name * @param string $primaryFieldName * @param string $requestFieldName * @param CollectionFactory $collectionFactory * @param RequestInterface $request * @param array $meta * @param array $data */ public function __construct( $name, $primaryFieldName, $requestFieldName, CollectionFactory $collectionFactory, RequestInterface $request, array $meta = [], array $data = [] ) { parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data); $this->collectionFactory = $collectionFactory;
        $this->collection = $this->collectionFactory->create();
        $this->request = $request;
    }

    /**
     * {@inheritdoc}
     */
    public function getData()
    {
        $this->getCollection();

        $arrItems = [
            'totalRecords' => $this->getCollection()->getSize(),
            'items' => [],
        ];

        foreach ($this->getCollection() as $item) {
            $arrItems['items'][] = $item->toArray([]);
        }

        return $arrItems;
    }

    /**
     * {@inheritdoc}
     */
    public function addFilter(\Magento\Framework\Api\Filter $filter)
    {
        $field = $filter->getField();

        if (in_array($field, ['id','title','created_at', 'is_active'])) {
            $filter->setField($field);
        }



        parent::addFilter($filter);
    }
}

In this file you can see, We have added the Codextblog\Customgrid\Model\ResourceModel\Post\CollectionFactory as a dependency in the constructor. This is the CollectionFactory class of another module called Customgrid. This module grid we will render in our custom tab. Here function getData provides the data array which Magento 2 use to render the grid data. Function addFilter is responsible for providing filter data whenever we apply a filter in the grid.

That’s it. After following all the steps, run following commands and check your Magento 2 admin product edit page. You will see the “Custom Grid Tab” that render the grid.

php bin/magento setup:upgrade

php bin/magento setup:di:compile

php bin/magento setup:static-content:deploy

php bin/magento c:c

If you liked this post, then please like us on Facebook and follow us on Twitter.