Magento2 | PWA | GraphQL

Add Products Grid and Custom Collection in UI Component form in custom module Magento2


This Magento2 module covers below points:

1. Display the products grid in the tab section for edit form data.

2. Create a tag and category on the fly from the edit form.

3. Display the Custom Collection Grid on the form widget.

You can find the complete module on GitHub at Magelearn_ProductsGrid

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/ProductsGrid

Add registration.php file in it:

<?php

use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(
    ComponentRegistrar::MODULE,
    'Magelearn_ProductsGrid',
    __DIR__
);
Add composer.json file in it:
{
    "name": "magelearn/module-productsgrid",
    "description": "Add Products Grid and Custom Collection in UI Component form in custom module Magento2.",
    "type": "magento2-module",
    "license": "OSL-3.0",
    "authors": [
        {
            "email": "vijaymrami@gmail.com",
            "name": "vijay rami"
        }
    ],
    "minimum-stability": "dev",
    "require": {},
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Magelearn\\ProductsGrid\\": ""
        }
    }
}
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_ProductsGrid" setup_version="1.0.0">
    </module>
</config>
Now to manage the Post (main data), Tags, Categories and Swatch data in the Database, we will create four different tables. Will also create three different tables to manage Post's Tag, categories and Post's Products.

Add etc/db_schema.xml file.
<?xml version="1.0"?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
  <table name="magelearn_item_post" resource="default" engine="innodb" comment="Item Table">
    <column xsi:type="int" name="item_id" padding="10" unsigned="true" nullable="false" identity="true" comment="Item ID"/>
    <column xsi:type="varchar" name="name" nullable="true" length="255" comment="Item Name"/>
    <column xsi:type="text" name="short_description" nullable="true" comment="Item Short Description"/>
    <column xsi:type="text" name="item_content" nullable="true" comment="Item Content"/>
    <column xsi:type="text" name="store_ids" nullable="false" comment="Store Id"/>
    <column xsi:type="varchar" name="image" nullable="true" length="255" comment="Item Image"/>
    <column xsi:type="int" name="enabled" padding="11" unsigned="false" nullable="true" identity="false" comment="Item Enabled"/>
    <column xsi:type="varchar" name="item_title_color" nullable="true" length="255" comment="Item Title Color"/>
    <column xsi:type="timestamp" name="updated_at" on_update="false" nullable="true"/>
    <column xsi:type="timestamp" name="created_at" on_update="false" nullable="true"/>
    <constraint xsi:type="primary" referenceId="PRIMARY">
      <column name="item_id"/>
    </constraint>
  </table>
  <table name="magelearn_item_tag" resource="default" engine="innodb" comment="Tag Table">
    <column xsi:type="int" name="tag_id" padding="10" unsigned="true" nullable="false" identity="true" comment="Tag ID"/>
    <column xsi:type="varchar" name="name" nullable="true" length="255" comment="Tag Name"/>
    <column xsi:type="text" name="description" nullable="true" comment="Tag Description"/>
    <column xsi:type="int" name="enabled" padding="11" unsigned="false" nullable="true" identity="false" comment="Tag Enabled"/>
    <column xsi:type="timestamp" name="updated_at" on_update="false" nullable="true"/>
    <column xsi:type="timestamp" name="created_at" on_update="false" nullable="true"/>
    <constraint xsi:type="primary" referenceId="PRIMARY">
      <column name="tag_id"/>
    </constraint>
  </table>
  <table name="magelearn_item_category" resource="default" engine="innodb" comment="Category Table">
    <column xsi:type="int" name="category_id" padding="10" unsigned="true" nullable="false" identity="true" comment="Category ID"/>
    <column xsi:type="varchar" name="name" nullable="true" length="255" comment="Category Name"/>
    <column xsi:type="int" name="enabled" padding="11" unsigned="false" nullable="true" identity="false" comment="Category Enabled"/>
    <column xsi:type="timestamp" name="updated_at" on_update="false" nullable="true"/>
    <column xsi:type="timestamp" name="created_at" on_update="false" nullable="true"/>
    <constraint xsi:type="primary" referenceId="PRIMARY">
      <column name="category_id"/>
    </constraint>
  </table>
  <table name="magelearn_item_post_tag" resource="default" engine="innodb" comment="Item To Tag Link Table">
    <column xsi:type="int" name="tag_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Tag ID"/>
    <column xsi:type="int" name="item_id" padding="10" unsigned="true" nullable="false" identity="false" comment="ITEM ID"/>
    <column xsi:type="int" name="position" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Position"/>
    <constraint xsi:type="primary" referenceId="PRIMARY">
      <column name="tag_id"/>
      <column name="item_id"/>
    </constraint>
    <constraint xsi:type="foreign" referenceId="MAGELEARN_ITEM_POST_TAG_ITEM_ID_MAGELEARN_ITEM_POST_ITEM_ID"
    	table="magelearn_item_post_tag"
    	column="item_id"
    	referenceTable="magelearn_item_post"
    	referenceColumn="item_id" onDelete="CASCADE"/>
    <constraint xsi:type="foreign" referenceId="MAGELEARN_ITEM_POST_TAG_TAG_ID_MAGELEARN_ITEM_TAG_TAG_ID"
    	table="magelearn_item_post_tag"
    	column="tag_id"
    	referenceTable="magelearn_item_tag"
    	referenceColumn="tag_id" onDelete="CASCADE"/>
    <constraint xsi:type="unique" referenceId="MAGELEARN_ITEM_POST_TAG_ITEM_ID_TAG_ID">
      <column name="item_id"/>
      <column name="tag_id"/>
    </constraint>
    <index referenceId="MAGELEARN_ITEM_POST_TAG_ITEM_ID" indexType="btree">
      <column name="item_id"/>
    </index>
    <index referenceId="MAGELEARN_ITEM_POST_TAG_TAG_ID" indexType="btree">
      <column name="tag_id"/>
    </index>
  </table>
  <table name="magelearn_item_post_category" resource="default" engine="innodb" comment="Category To Item Link Table">
    <column xsi:type="int" name="category_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Category ID"/>
    <column xsi:type="int" name="item_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Item ID"/>
    <column xsi:type="int" name="position" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Position"/>
    <constraint xsi:type="primary" referenceId="PRIMARY">
      <column name="category_id"/>
      <column name="item_id"/>
    </constraint>
    <constraint xsi:type="foreign" referenceId="MAGELEARN_ITEM_POST_CTGR_CTGR_ID_MAGELEARN_ITEM_CTGR_CTGR_ID"
    	table="magelearn_item_post_category" 
    	column="category_id"
    	referenceTable="magelearn_item_category"
    	referenceColumn="category_id" onDelete="CASCADE"/>
    <constraint xsi:type="foreign" referenceId="MAGELEARN_ITEM_POST_CATEGORY_ITEM_ID_MAGELEARN_ITEM_POST_ITEM_ID"
    	table="magelearn_item_post_category"
    	column="item_id"
    	referenceTable="magelearn_item_post"
    	referenceColumn="item_id" onDelete="CASCADE"/>
    <constraint xsi:type="unique" referenceId="MAGELEARN_ITEM_POST_CATEGORY_CATEGORY_ID_ITEM_ID">
      <column name="category_id"/>
      <column name="item_id"/>
    </constraint>
    <index referenceId="MAGELEARN_ITEM_POST_CATEGORY_CATEGORY_ID" indexType="btree">
      <column name="category_id"/>
    </index>
    <index referenceId="MAGELEARN_ITEM_POST_CATEGORY_ITEM_ID" indexType="btree">
      <column name="item_id"/>
    </index>
  </table>
  <table name="magelearn_item_post_product" resource="default" engine="innodb" comment="Item To Product Link Table">
    <column xsi:type="int" name="item_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Item ID"/>
    <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Entity ID"/>
    <column xsi:type="int" name="position" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Position"/>
    <constraint xsi:type="primary" referenceId="PRIMARY">
      <column name="item_id"/>
      <column name="entity_id"/>
    </constraint>
    <constraint xsi:type="foreign" referenceId="MAGELEARN_ITEM_POST_PRODUCT_ITEM_ID_MAGELEARN_ITEM_POST_ITEM_ID"
    	table="magelearn_item_post_product"
    	column="item_id"
    	referenceTable="magelearn_item_post"
    	referenceColumn="item_id" onDelete="CASCADE"/>
    <constraint xsi:type="foreign" referenceId="MAGELEARN_ITEM_POST_PRD_ENTT_ID_CAT_PRD_ENTT_ENTT_ID"
    	table="magelearn_item_post_product"
    	column="entity_id"
    	referenceTable="catalog_product_entity"
    	referenceColumn="entity_id" onDelete="CASCADE"/>
    <constraint xsi:type="unique" referenceId="MAGELEARN_ITEM_POST_PRODUCT_ITEM_ID_ENTITY_ID">
      <column name="item_id"/>
      <column name="entity_id"/>
    </constraint>
    <index referenceId="MAGELEARN_ITEM_POST_PRODUCT_ITEM_ID" indexType="btree">
      <column name="item_id"/>
    </index>
    <index referenceId="MAGELEARN_ITEM_POST_PRODUCT_ENTITY_ID" indexType="btree">
      <column name="entity_id"/>
    </index>
  </table>
  <table name="magelearn_item_post_swatch" resource="default" engine="innodb" comment="Post Item To Swatch Link Table">
  	<column xsi:type="int" name="swatch_id" padding="10" unsigned="true" nullable="false" identity="true" comment="Swatch ID"/>
    <column xsi:type="int" name="position" padding="10" unsigned="true" nullable="false" identity="false" comment="Position"/>
    <column xsi:type="text" name="description" nullable="true" comment="Swatch Description"/>
    <column xsi:type="int" name="item_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Item ID"/>
    <column xsi:type="varchar" name="image" nullable="true" length="255" comment="Item Swatch Image"/>
    <constraint xsi:type="primary" referenceId="PRIMARY">
      <column name="swatch_id"/>
      <column name="item_id"/>
    </constraint>
    <constraint xsi:type="foreign" referenceId="MAGELEARN_ITEM_POST_SWATCH_ITEM_ID_MAGELEARN_ITEM_POST_ITEM_ID"
    	table="magelearn_item_post_swatch"
    	column="item_id"
    	referenceTable="magelearn_item_post"
    	referenceColumn="item_id" onDelete="CASCADE"/>
    <constraint xsi:type="unique" referenceId="MAGELEARN_ITEM_POST_SWATCH_SWATCH_ID_ITEM_ID">
      <column name="item_id"/>
      <column name="swatch_id"/>
    </constraint>
    <index referenceId="MAGELEARN_ITEM_POST_SWATCH_ITEM_ID" indexType="btree">
      <column name="item_id"/>
    </index>
    <index referenceId="MAGELEARN_ITEM_POST_SWATCH_SWATCH_ID" indexType="btree">
      <column name="swatch_id"/>
    </index>
  </table>
</schema>
We will also add our install Data script to add custom product attribute in the Edit product form.
It is a select type attribute that will display a list of Posts in the selection list for that product.

Add Setup/InstallData.php file.
<?php

namespace Magelearn\ProductsGrid\Setup;

use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Catalog\Setup\CategorySetupFactory;
use Magento\Eav\Model\Entity\Attribute\Set as AttributeSet;
use Magento\Eav\Model\Entity\Attribute\SetFactory as AttributeSetFactory;

class InstallData implements InstallDataInterface
{
  private $eavSetupFactory;

    public function __construct(EavSetupFactory $eavSetupFactory)
    {
        $this->eavSetupFactory = $eavSetupFactory;
    }

    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
        $productTypes = [
            \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE,
            \Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE,
            \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL,
            \Magento\Downloadable\Model\Product\Type::TYPE_DOWNLOADABLE,
            \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE,
        ];
        $productTypes = join(',', $productTypes);
        $eavSetup->addAttribute(
            \Magento\Catalog\Model\Product::ENTITY,
            'magelearn_post',
            [
        'group' => 'Content',
                'sort_order' => 150,
                'type' => 'int',
                'backend' => '',
                'frontend' => '',
                'label' => 'Item Post',
                'input' => 'select',
                'class' => '',
                'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL,
        'source' => 'Magelearn\ProductsGrid\Model\Entity\Attribute\Backend\ItemPost',
                'visible' => true,
                'required' => false,
                'user_defined' => true,
                'searchable' => false,
                'filterable' => false,
                'comparable' => false,
                'visible_in_advanced_search' => false,
                'visible_on_front' => false,
                'used_in_product_listing' => true,
                'unique' => false,
                'apply_to' => $productTypes,
                'is_used_in_grid' => false,
                'is_visible_in_grid' => false,
                'is_filterable_in_grid' => false,
            ]
        );
    }
}

As per the Highlighted code above add Model/Entity/Attribute/Backend/ItemPost.php file.

<?php
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Magelearn\ProductsGrid\Model\Entity\Attribute\Backend;

use Magelearn\ProductsGrid\Model\ResourceModel\Post\CollectionFactory;

class ItemPost extends \Magento\Eav\Model\Entity\Attribute\Source\AbstractSource
{
    /**
     * @var CollectionFactory
     */
    public $postCollectionFactory;

    /**
     * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\CollectionFactory $attrOptionCollectionFactory
     * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\OptionFactory $attrOptionFactory
     * @param \Magento\Store\Model\ResourceModel\Store\CollectionFactory $storeCollectionFactory
     * @codeCoverageIgnore
     */
    public function __construct(
        CollectionFactory $postCollectionFactory
    ) {
        $this->_postCollectionFactory = $postCollectionFactory;
    }
    
    /**
     * Retrieve all options array
     *
     * @return array
     */
    public function getAllOptions()
    {
        if ($this->_options === null) {
            $result[] = ['value'=>'', 'label'=>__(' ')];
            
            $posts = $this->_postCollectionFactory->create();
            $posts->addFieldToFilter('enabled', '1');
            if(count($posts)>0){
                foreach($posts as $post){
                    $result[] = ['value'=>$post->getId(), 'label'=>$post->getName()];
                }
            }
            
            $this->_options = $result;

        }
        return $this->_options;
    }

}

Now, create di.xml file inside etc folder. 

This file will define the necessary nodes to display data in the admin grid.

<?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\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
        <arguments>
            <argument name="collections" xsi:type="array">
                <item name="magelearn_item_post_listing_data_source" xsi:type="string">Magelearn\ProductsGrid\Model\ResourceModel\Post\Grid\Collection</item>
                <item name="magelearn_item_tag_listing_data_source" xsi:type="string">Magelearn\ProductsGrid\Model\ResourceModel\Tag\Grid\Collection</item>
                <item name="magelearn_item_category_listing_data_source" xsi:type="string">Magelearn\ProductsGrid\Model\ResourceModel\Category\Grid\Collection</item>
            </argument>
        </arguments>
    </type>
    <virtualType name="Magelearn\ProductsGrid\Model\ResourceModel\Post\Grid\Collection" type="Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult">
        <arguments>
            <argument name="mainTable" xsi:type="string">magelearn_item_post</argument>
            <argument name="resourceModel" xsi:type="string">Magelearn\ProductsGrid\Model\ResourceModel\Post</argument>
        </arguments>
    </virtualType>
    <virtualType name="Magelearn\ProductsGrid\Model\ResourceModel\Tag\Grid\Collection" type="Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult">
        <arguments>
            <argument name="mainTable" xsi:type="string">magelearn_item_tag</argument>
            <argument name="resourceModel" xsi:type="string">Magelearn\ProductsGrid\Model\ResourceModel\Tag</argument>
        </arguments>
    </virtualType>
    <virtualType name="Magelearn\ProductsGrid\Model\ResourceModel\Category\Grid\Collection" type="Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult">
        <arguments>
            <argument name="mainTable" xsi:type="string">magelearn_item_category</argument>
            <argument name="resourceModel" xsi:type="string">Magelearn\ProductsGrid\Model\ResourceModel\Category</argument>
        </arguments>
    </virtualType>
    <preference for="Magelearn\ProductsGrid\Api\Data\PostInterface" type="Magelearn\ProductsGrid\Model\Post" />
    <preference for="Magelearn\ProductsGrid\Api\Data\TagInterface" type="Magelearn\ProductsGrid\Model\Tag" />
    <preference for="Magelearn\ProductsGrid\Api\Data\CategoryInterface" type="Magelearn\ProductsGrid\Model\Category" />
    <preference for="Magelearn\ProductsGrid\Api\Data\SwatchInterface" type="Magelearn\ProductsGrid\Model\Swatch" />
    <preference for="Magelearn\ProductsGrid\Api\ItemRepositoryInterface" type="Magelearn\ProductsGrid\Model\ItemRepository" />
    <preference for="Magelearn\ProductsGrid\Api\SwatchRepositoryInterface" type="Magelearn\ProductsGrid\Model\SwatchRepository" />

    <preference for="Magelearn\ProductsGrid\Api\Data\PostSearchResultInterface" type="Magento\Framework\Api\SearchResults" />
    <preference for="Magelearn\ProductsGrid\Api\Data\CategorySearchResultInterface" type="Magento\Framework\Api\SearchResults" />
    <preference for="Magelearn\ProductsGrid\Api\Data\TagSearchResultInterface" type="Magento\Framework\Api\SearchResults" />
    <preference for="Magelearn\ProductsGrid\Api\Data\SwatchSearchResultsInterface" type="Magento\Framework\Api\SearchResults"/>
</config>

We will also add 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">
    <type name="Magento\Ui\Model\Export\MetadataProvider">
        <arguments>
            <argument name="data" xsi:type="array">
                <item name="magelearn_item_post_listing" xsi:type="array">
                    <item name="created_at" xsi:type="string">created_at</item>
                    <item name="updated_at" xsi:type="string">updated_at</item>
                </item>
                <item name="magelearn_item_tag_listing" xsi:type="array">
                    <item name="created_at" xsi:type="string">created_at</item>
                    <item name="updated_at" xsi:type="string">updated_at</item>
                </item>
                <item name="magelearn_item_category_listing" xsi:type="array">
                    <item name="created_at" xsi:type="string">created_at</item>
                    <item name="updated_at" xsi:type="string">updated_at</item>
                </item>
            </argument>
        </arguments>
    </type>
    <type name="Magento\Catalog\Model\ResourceModel\Product\Collection">
        <plugin name="item-post-product-sort" type="Magelearn\ProductsGrid\Plugin\Catalog\AttributeSort"/>
    </type>
</config>

By Defining Arguments in type name="Magento\Ui\Model\Export\MetadataProvider" node, we make sure that created_at and updated_at date in the export CSV file is in the same format which is displayed in the Admin grid list.

We also defined our Plugin class to sort the products that will be displayed inside the post products tab.

Add Plugin/Catalog/AttributeSort.php file.

<?php

namespace Magelearn\ProductsGrid\Plugin\Catalog;

use Magento\Catalog\Model\ResourceModel\Product\Collection;
use Magento\Framework\App\RequestInterface;
use Magelearn\ProductsGrid\Helper\Data;

/**
 * Class AttributeSort
 * @package Magelearn\ProductsGrid\Plugin\Catalog
 */
class AttributeSort
{
    /**
     * @var Data
     */
    protected $helper;

    /**
     * @var RequestInterface
     */
    protected $request;

    /**
     * AttributeSort constructor.
     *
     * @param RequestInterface $request
     * @param Data $helper
     */
    public function __construct(
        RequestInterface $request,
        Data $helper
    ) {
        $this->helper  = $helper;
        $this->request = $request;
    }

    public function aroundAddAttributeToSort(
        Collection $productCollection,
        callable $proceed,
        $attribute,
        $dir
    ) {
        if ($attribute === 'position' &&
            in_array(
                $this->request->getFullActionName(),
                ['magelearn_productsgrid_post_products', 'magelearn_productsgrid_post_productsGrid'],
                true
            )
        ) {
            $productCollection->getSelect()->order('position ' . $dir);

            return $productCollection;
        }

        return $proceed($attribute, $dir);
    }
}

Before Proceed further, we will add etc/adminhtml/menu.xml file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
    <menu>
        <add id="Magelearn_ProductsGrid::item" title="Item Grid" module="Magelearn_ProductsGrid" sortOrder="110" resource="Magelearn_ProductsGrid::item"/>
        <add id="Magelearn_ProductsGrid::post" title="Manage Posts" module="Magelearn_ProductsGrid" sortOrder="10" action="magelearn_productsgrid/post" resource="Magelearn_ProductsGrid::post" parent="Magelearn_ProductsGrid::item"/>
        <add id="Magelearn_ProductsGrid::tag" title="Manage Tags" module="Magelearn_ProductsGrid" sortOrder="20" action="magelearn_productsgrid/tag" resource="Magelearn_ProductsGrid::tag" parent="Magelearn_ProductsGrid::item"/>
        <add id="Magelearn_ProductsGrid::category" title="Categories" module="Magelearn_ProductsGrid" sortOrder="30" action="magelearn_productsgrid/category" resource="Magelearn_ProductsGrid::category" parent="Magelearn_ProductsGrid::item"/>
    </menu>
</config>

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="magelearn_productsgrid" frontName="magelearn_productsgrid">
            <module name="Magelearn_ProductsGrid"/>
        </route>
    </router>
</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="Magelearn_ProductsGrid::item" title="Magelearn Item Grid" sortOrder="25">
                        <resource id="Magelearn_ProductsGrid::post" title="Posts" sortOrder="10"/>
                        <resource id="Magelearn_ProductsGrid::tag" title="Tags" sortOrder="20"/>
                        <resource id="Magelearn_ProductsGrid::category" title="Categories" sortOrder="30"/>
                    </resource>
            </resource>
        </resources>
    </acl>
</config>

Now for backend management, We will start to create files as defined in etc/di.xml nodes.

Create PostInterface.php file inside Api/Data folder.
<?php

namespace Magelearn\ProductsGrid\Api\Data;

/**
 * Interface PostInterface
 * @package Magelearn\ProductsGrid\Api\Data
 */
interface PostInterface
{
    /**
     * Constants used as data array keys
     */
    const ITEM_ID           = 'item_id';
    const NAME              = 'name';
    const SHORT_DESCRIPTION = 'short_description';
    const ITEM_CONTENT      = 'item_content';
    const STORE_IDS         = 'store_ids';
    const IMAGE             = 'image';
    const ENABLED           = 'enabled';
    const UPDATED_AT        = 'updated_at';
    const CREATED_AT        = 'created_at';
    const CATEGORY_IDS      = 'category_ids';
    const TAG_IDS           = 'tag_ids';
    const ITEM_TITLE_COLOR  = 'item_title_color';

    const ATTRIBUTES = [
        self::ITEM_ID,
        self::NAME,
        self::SHORT_DESCRIPTION,
        self::ITEM_CONTENT,
        self::STORE_IDS,
        self::IMAGE,
        self::ENABLED,
        self::CATEGORY_IDS,
        self::TAG_IDS,
        self::ITEM_TITLE_COLOR
    ];

    /**
     * Get Item id
     *
     * @return int|null
     */
    public function getId();

    /**
     * Set Item id
     *
     * @param int $id
     *
     * @return $this
     */
    public function setId($id);

    /**
     * Get Item Name
     *
     * @return string/null
     */
    public function getName();

    /**
     * Set Item Name
     *
     * @param string $name
     *
     * @return $this
     */
    public function setName($name);

    /**
     * Get Item Short Description
     *
     * @return string/null
     */
    public function getShortDescription();

    /**
     * Set Item Short Description
     *
     * @param string $content
     *
     * @return $this
     */
    public function setShortDescription($content);

    /**
     * Get Item Content
     *
     * @return string/null
     */
    public function getItemContent();

    /**
     * Set Item Content
     *
     * @param string $content
     *
     * @return $this
     */
    public function setItemContent($content);

    /**
     * Get Item Store Id
     *
     * @return int/null
     */
    public function getStoreIds();

    /**
     * Set Item Store Id
     *
     * @param int $storeId
     *
     * @return $this
     */
    public function setStoreIds($storeId);

    /**
     * Get Item Image
     *
     * @return string/null
     */
    public function getImage();

    /**
     * Set Item Image
     *
     * @param string $content
     *
     * @return $this
     */
    public function setImage($content);

    /**
     * Get Item Enabled
     *
     * @return int/null
     */
    public function getEnabled();

    /**
     * Set Item Enabled
     *
     * @param int $enabled
     *
     * @return $this
     */
    public function setEnabled($enabled);

    /**
     * @return string|null
     */
    public function getCreatedAt();

    /**
     * @param string $createdAt
     *
     * @return $this
     */
    public function setCreatedAt($createdAt);

    /**
     * Get Item updated date
     *
     * @return string|null
     */
    public function getUpdatedAt();

    /**
     * Set Item updated date
     *
     * @param string $updatedAt
     *
     * @return $this
     */
    public function setUpdatedAt($updatedAt);

    /**
     * @return int[]|null
     */
    public function getCategoryIds();

    /**
     * @param int[] $array
     *
     * @return $this
     */
    public function setCategoryIds($array);

    /**
     * @return int[]|null
     */
    public function getTagIds();

    /**
     * @param int[] $array
     *
     * @return $this
     */
    public function setTagIds($array);
    
    /**
     * Get Item Title Color
     *
     * @return string/null
     */
    public function getItemTitleColor();
    
    /**
     * Set Item Title Color
     *
     * @param string $itemTitleColor
     *
     * @return $this
     */
    public function setItemTitleColor($itemTitleColor);
}
Create PostSearchResultInterface.php file inside Api/Data folder.
<?php

namespace Magelearn\ProductsGrid\Api\Data;

use Magento\Framework\Api\SearchResultsInterface;

/**
 * Interface PostSearchResultInterface
 * @api
 */
interface PostSearchResultInterface extends SearchResultsInterface
{
    /**
     * @return \Magelearn\ProductsGrid\Api\Data\PostInterface[]
     */
    public function getItems();

    /**
     * @param \Magelearn\ProductsGrid\Api\Data\PostInterface[] $items
     * @return $this
     */
    public function setItems(array $items = null);
}

Create CategoryInterface.php file inside Api/Data folder.
<?php

namespace Magelearn\ProductsGrid\Api\Data;

/**
 * Interface CategoryInterface
 * @package Magelearn\ProductsGrid\Api\Data
 */
interface CategoryInterface
{
    /**
     * Constants used as data array keys
     */
    const CATEGORY_ID      = 'category_id';
    const NAME             = 'name';
    const ENABLED          = 'enabled';
    const UPDATED_AT       = 'updated_at';
    const CREATED_AT       = 'created_at';

    const ATTRIBUTES = [
        self::CATEGORY_ID,
        self::NAME,
        self::ENABLED
    ];

    /**
     * @return int|null
     */
    public function getId();

    /**
     * @param int $id
     *
     * @return $this
     */
    public function setId($id);

    /**
     * @return string/null
     */
    public function getName();

    /**
     * @param string $name
     *
     * @return $this
     */
    public function setName($name);
    
    /**
     * Get Cateory Enabled
     *
     * @return int/null
     */
    public function getEnabled();
    
    /**
     * Set Cateory Enabled
     *
     * @param int $enabled
     *
     * @return $this
     */
    public function setEnabled($enabled);

    /**
     * @return string|null
     */
    public function getCreatedAt();

    /**
     * @param string $createdAt
     *
     * @return $this
     */
    public function setCreatedAt($createdAt);

    /**
     * Get Post updated date
     *
     * @return string|null
     */
    public function getUpdatedAt();

    /**
     * Set Post updated date
     *
     * @param string $updatedAt
     *
     * @return $this
     */
    public function setUpdatedAt($updatedAt);
}

Create CategorySearchResultInterface.php file inside Api/Data folder.
<?php

namespace Magelearn\ProductsGrid\Api\Data;

use Magento\Framework\Api\SearchResultsInterface;

/**
 * Interface CategorySearchResultInterface
 * @package Magelearn\ProductsGrid\Api\Data
 */
interface CategorySearchResultInterface extends SearchResultsInterface
{
    /**
     * @return \Magelearn\ProductsGrid\Api\Data\CategoryInterface[]
     */
    public function getItems();

    /**
     * @param \Magelearn\ProductsGrid\Api\Data\CategoryInterface[] $items
     * @return $this
     */
    public function setItems(array $items = null);
}

Create TagInterface.php file inside Api/Data folder.
<?php

namespace Magelearn\ProductsGrid\Api\Data;

/**
 * Interface TagInterface
 * @package Magelearn\ProductsGrid\Api\Data
 */
interface TagInterface
{
    /**
     * Constants used as data array keys
     */
    const TAG_ID           = 'tag_id';
    const NAME             = 'name';
    const DESCRIPTION      = 'description';
    const ENABLED          = 'enabled';
    const UPDATED_AT       = 'updated_at';
    const CREATED_AT       = 'created_at';

    const ATTRIBUTES = [
        self::TAG_ID,
        self::NAME,
        self::DESCRIPTION,
        self::ENABLED
    ];

    /**
     * Get Item id
     *
     * @return int|null
     */
    public function getId();

    /**
     * Set Item id
     *
     * @param int $id
     *
     * @return $this
     */
    public function setId($id);

    /**
     * Get Item Name
     *
     * @return string/null
     */
    public function getName();

    /**
     * Set Item Name
     *
     * @param string $name
     *
     * @return $this
     */
    public function setName($name);
    
    /**
     * Get Item Description
     *
     * @return string/null
     */
    public function getDescription();
    
    /**
     * Set Item Short Description
     *
     * @param string $content
     *
     * @return $this
     */
    public function setDescription($content);
    
    /**
     * Get Item Enabled
     *
     * @return int/null
     */
    public function getEnabled();
    
    /**
     * Set Item Enabled
     *
     * @param int $enabled
     *
     * @return $this
     */
    public function setEnabled($enabled);

    /**
     * @return string|null
     */
    public function getCreatedAt();

    /**
     * @param string $createdAt
     *
     * @return $this
     */
    public function setCreatedAt($createdAt);

    /**
     * Get Item updated date
     *
     * @return string|null
     */
    public function getUpdatedAt();

    /**
     * Set Item updated date
     *
     * @param string $updatedAt
     *
     * @return $this
     */
    public function setUpdatedAt($updatedAt);
}

Create TagSearchResultInterface.php file inside Api/Data folder.
<?php

namespace Magelearn\ProductsGrid\Api\Data;

use Magento\Framework\Api\SearchResultsInterface;

/**
 * Interface TagSearchResultInterface
 * @package Magelearn\ProductsGrid\Api\Data
 */
interface TagSearchResultInterface extends SearchResultsInterface
{
    /**
     * @return \Magelearn\ProductsGrid\Api\Data\TagInterface[]
     */
    public function getItems();

    /**
     * @param \Magelearn\ProductsGrid\Api\Data\TagInterface[] $items
     * @return $this
     */
    public function setItems(array $items = null);
}
Add Api/Data/SwatchInterface.php file.
<?php

declare(strict_types=1);

namespace Magelearn\ProductsGrid\Api\Data;

interface SwatchInterface
{
    const SWATCH_ID = 'swatch_id';
    const POSITION = 'position';
    const DESCRIPTION = 'description';
    const ITEM_ID = 'item_id';
    const IMAGE = 'image';

    /**
     * Get swatch_id
     * @return int|null
     */
    public function getSwatchId();

    /**
     * Set swatch_id
     * @param int $swatchId
     * @return \Magelearn\ProductsGrid\Api\Data\SwatchInterface
     */
    public function setSwatchId(int $swatchId): SwatchInterface;

    /**
     * Get position
     * @return int
     */
    public function getPosition(): int;

    /**
     * Set position
     * @param int $position
     * @return \Magelearn\ProductsGrid\Api\Data\SwatchInterface
     */
    public function setPosition(int $position): SwatchInterface;

    /**
     * Get description
     * @return string|NULL
     */
    public function getDescription(): ?string;

    /**
     * Set description
     * @param string $description
     * @return \Magelearn\ProductsGrid\Api\Data\SwatchInterface
     */
    public function setDescription(?string $description): SwatchInterface;

    /**
     * Get Item id
     * @return int
     */
    public function getItemId(): int;

    /**
     * Set item id
     * @param int $itemId
     * @return \Magelearn\ProductsGrid\Api\Data\SwatchInterface
     */
    public function setItemId(int $itemId): SwatchInterface;

    /**
     * Get image
     * @return string|null
     */
    public function getImage();

    /**
     * Set image
     * @param string $image
     * @return \Magelearn\ProductsGrid\Api\Data\SwatchInterface
     */
    public function setImage(string $image): SwatchInterface;
}
Add Api/Data/SwatchSearchResultsInterface.php file.
<?php

declare(strict_types=1);

namespace Magelearn\ProductsGrid\Api\Data;

interface SwatchSearchResultsInterface extends \Magento\Framework\Api\SearchResultsInterface
{
    /**
     * @return \Magelearn\ProductsGrid\Api\Data\SwatchInterface[]
     */
    public function getItems();

    /**
     * @param \Magelearn\ProductsGrid\Api\Data\SwatchInterface[] $items
     * @return $this
     */
    public function setItems(array $items);
}
Now Add Api/ItemRepositoryInterface.php file.
<?php

namespace Magelearn\ProductsGrid\Api;

use Exception;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\NoSuchEntityException;

/**
 * Class ItemRepositoryInterface
 * @package Magelearn\ProductsGrid\Api
 */
interface ItemRepositoryInterface
{
    /**
     * @return \Magelearn\ProductsGrid\Api\Data\PostInterface[]
     */
    public function getAllPost();

    /**
     * @param string $tagName
     *
     * @return \Magelearn\ProductsGrid\Api\Data\PostInterface[]
     */
    public function getPostByTagName($tagName);

    /**
     * @param string $postId
     *
     * @return \Magento\Catalog\Api\Data\ProductInterface[]
     */
    public function getProductByPost($postId);

    /**
     * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
     *
     * @return \Magelearn\ProductsGrid\Api\Data\SearchResult\PostSearchResultInterface
     * @throws NoSuchEntityException
     */
    public function getPostList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria);

    /**
     * Create Post
     *
     * @param \Magelearn\ProductsGrid\Api\Data\PostInterface $post
     *
     * @return \Magelearn\ProductsGrid\Api\Data\PostInterface
     * @throws Exception
     */
    public function createPost($post);

    /**
     * Delete Post
     *
     * @param string $postId
     *
     * @return string
     */
    public function deletePost($postId);

    /**
     * @param string $postId
     * @param \Magelearn\ProductsGrid\Api\Data\PostInterface $post
     *
     * @return \Magelearn\ProductsGrid\Api\Data\PostInterface
     * @throws InputException
     * @throws NoSuchEntityException
     * @throws Exception
     */
    public function updatePost($postId, $post);

    /**
     * Get All Tag
     *
     * @return \Magelearn\ProductsGrid\Api\Data\TagInterface[]
     */
    public function getAllTag();

    /**
     * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
     *
     * @return \Magelearn\ProductsGrid\Api\Data\SearchResult\TagSearchResultInterface
     */
    public function getTagList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria);

    /**
     * Create Post
     *
     * @param \Magelearn\ProductsGrid\Api\Data\TagInterface $tag
     *
     * @return \Magelearn\ProductsGrid\Api\Data\TagInterface
     * @throws Exception
     */
    public function createTag($tag);

    /**
     * Delete Tag
     *
     * @param string $tagId
     *
     * @return string
     */
    public function deleteTag($tagId);

    /**
     * @param string $tagId
     * @param \Magelearn\ProductsGrid\Api\Data\TagInterface $tag
     *
     * @return \Magelearn\ProductsGrid\Api\Data\TagInterface
     * @throws InputException
     * @throws NoSuchEntityException
     * @throws Exception
     */
    public function updateTag($tagId, $tag);

    /**
     * Get All Category
     *
     * @return \Magelearn\ProductsGrid\Api\Data\CategoryInterface[]
     */
    public function getAllCategory();

    /**
     * Get Category List
     *
     * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
     *
     * @return \Magelearn\ProductsGrid\Api\Data\SearchResult\CategorySearchResultInterface
     */
    public function getCategoryList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria);

    /**
     * @param string $categoryId
     *
     * @return \Magelearn\ProductsGrid\Api\Data\PostInterface[]
     */
    public function getPostsByCategoryId($categoryId);

    /**
     * @param string $postId
     *
     * @return \Magelearn\ProductsGrid\Api\Data\CategoryInterface[]
     */
    public function getCategoriesByPostId($postId);

    /**
     * Create Category
     *
     * @param \Magelearn\ProductsGrid\Api\Data\CategoryInterface $category
     *
     * @return \Magelearn\ProductsGrid\Api\Data\CategoryInterface
     * @throws Exception
     */
    public function createCategory($category);

    /**
     * Delete Category
     *
     * @param string $categoryId
     *
     * @return string
     */
    public function deleteCategory($categoryId);

    /**
     * @param string $categoryId
     * @param \Magelearn\ProductsGrid\Api\Data\CategoryInterface $category
     *
     * @return \Magelearn\ProductsGrid\Api\Data\CategoryInterface
     * @throws InputException
     * @throws NoSuchEntityException
     * @throws Exception
     */
    public function updateCategory($categoryId, $category);
}

Add Api/SwatchRepositoryInterface.php file.

<?php
declare(strict_types=1);
namespace Magelearn\ProductsGrid\Api;

interface SwatchRepositoryInterface
{
    /**
     * Save Swatch
     * @param \Magelearn\ProductsGrid\Api\Data\SwatchInterface $swatch
     * @return \Magelearn\ProductsGrid\Api\Data\SwatchInterface
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function save(
        \Magelearn\ProductsGrid\Api\Data\SwatchInterface $swatch
    );

    /**
     * Retrieve Swatch
     * @param string $swatchId
     * @return \Magelearn\ProductsGrid\Api\Data\SwatchInterface
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function getById($swatchId);

    /**
     * Retrieve Swatch
     * @param string $postId
     * @return \Magelearn\ProductsGrid\Model\ResourceModel\Step\Collection
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function getListByPostId($postId);

    /**
     * Delete Swatch
     * @param \Magelearn\ProductsGrid\Api\Data\SwatchInterface $swatch
     * @return bool true on success
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function delete(
        \Magelearn\ProductsGrid\Api\Data\SwatchInterface $swatch
    );

    /**
     * Delete Swatch by ID
     * @param string $swatchId
     * @return bool true on success
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function deleteById($swatchId);
}

Add Model/ItemRepository.php file.

<?php

namespace Magelearn\ProductsGrid\Model;

use Exception;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface;
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\DataObject;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
use Magento\Sales\Model\ResourceModel\Collection\AbstractCollection as SalesAbstractCollection;
use Magento\Framework\Stdlib\DateTime\DateTime;
use Magelearn\ProductsGrid\Api\ItemRepositoryInterface;
use Magelearn\ProductsGrid\Api\Data\CategoryInterface;
use Magelearn\ProductsGrid\Api\Data\PostInterface;
use Magelearn\ProductsGrid\Api\Data\TagInterface;
use Magelearn\ProductsGrid\Helper\Data;

/**
 * Class ItemRepository
 * @package Magelearn\ProductsGrid\Model
 */
class ItemRepository implements ItemRepositoryInterface
{
    /**
     * @var Data
     */
    protected $_helperData;

    /**
     * @var DateTime
     */
    protected $date;

    /**
     * @var CustomerRepositoryInterface
     */
    protected $_customerRepositoryInterface;

    /**
     * @var CollectionProcessorInterface
     */
    protected $collectionProcessor;

    /**
     * @var RequestInterface
     */
    protected $_request;

    /**
     * ItemRepository constructor.
     *
     * @param Data $helperData
     * @param CustomerRepositoryInterface $customerRepositoryInterface
     * @param CollectionProcessorInterface $collectionProcessor
     * @param RequestInterface $request
     * @param DateTime $date
     */
    public function __construct(
        Data $helperData,
        CustomerRepositoryInterface $customerRepositoryInterface,
        CollectionProcessorInterface $collectionProcessor,
        RequestInterface $request,
        DateTime $date
    ) {
        $this->_request                     = $request;
        $this->_helperData                  = $helperData;
        $this->_customerRepositoryInterface = $customerRepositoryInterface;
        $this->date                         = $date;
        $this->collectionProcessor          = $collectionProcessor;
    }

    /**
     * @inheritDoc
     */
    public function getAllPost()
    {
        $collection = $this->_helperData->getFactoryByType()->create()->getCollection();

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

    /**
     * @inheritDoc
     */
    public function getPostByTagName($tagName)
    {
        $tag = $this->_helperData->getFactoryByType('tag')->create()->getCollection()
            ->addFieldToFilter('name', $tagName)->getFirstItem();

        return $tag->getSelectedPostsCollection()->getItems();
    }

    /**
     * @inheritDoc
     */
    public function getProductByPost($postId)
    {
        $post = $this->_helperData->getFactoryByType()->create()->load($postId);

        return $post->getSelectedProductsCollection()->getItems();
    }

    /**
     * @inheritDoc
     */
    public function getPostList(SearchCriteriaInterface $searchCriteria)
    {
        $collection = $this->_helperData->getPostCollection();

        return $this->getListEntity($collection, $searchCriteria);
    }

    /**
     * @param PostInterface $post
     *
     * @return PostInterface
     */
    public function createPost($post)
    {
        $data = $post->getData();

        if ($this->checkPostData($data)) {
            $this->prepareData($data);
            $post->addData($data);
            $post->save();
        }

        return $post;
    }

    /**
     * @param string $postId
     *
     * @return string|null
     * @throws Exception
     */
    public function deletePost($postId)
    {
        $post = $this->_helperData->getFactoryByType()->create()->load($postId);

        if ($post) {
            $post->delete();

            return true;
        }

        return false;
    }

    /**
     * @inheritDoc
     */
    public function updatePost($postId, $post)
    {
        if (empty($postId)) {
            throw new InputException(__('Invalid post id %1', $postId));
        }
        $subPost = $this->_helperData->getFactoryByType()->create()->load($postId);

        if (!$subPost->getId()) {
            throw new NoSuchEntityException(
                __(
                    'The "%1" Post doesn\'t exist.',
                    $postId
                )
            );
        }

        $subPost->addData($post->getData())->save();

        return $subPost;
    }

    /**
     * @inheritDoc
     */
    public function getAllTag()
    {
        $collection = $this->_helperData->getFactoryByType('tag')->create()->getCollection();

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

    /**
     * @inheritDoc
     */
    public function getTagList(SearchCriteriaInterface $searchCriteria)
    {
        $collection = $this->_helperData->getFactoryByType('tag')->create()->getCollection();

        return $this->getListEntity($collection, $searchCriteria);
    }

    /**
     * @param TagInterface $tag
     *
     * @return TagInterface
     */
    public function createTag($tag)
    {
        if (!empty($tag->getName())) {
            if (empty($tag->getEnabled())) {
                $tag->setEnabled(1);
            }
            if (empty($tag->getCreatedAt())) {
                $tag->setCreatedAt($this->date->date());
            }
            $tag->save();
        }

        return $tag;
    }

    /**
     * @param string $tagId
     *
     * @return bool|string
     * @throws Exception
     */
    public function deleteTag($tagId)
    {
        $tag = $this->_helperData->getFactoryByType('tag')->create()->load($tagId);

        if ($tag) {
            $tag->delete();

            return true;
        }

        return false;
    }

    /**
     * @inheritDoc
     */
    public function updateTag($tagId, $tag)
    {
        if (empty($tagId)) {
            throw new InputException(__('Invalid tag id %1', $tagId));
        }
        $subTag = $this->_helperData->getFactoryByType('tag')->create()->load($tagId);

        if (!$subTag->getId()) {
            throw new NoSuchEntityException(
                __(
                    'The "%1" Tag doesn\'t exist.',
                    $tagId
                )
            );
        }

        $subTag->addData($tag->getData())->save();

        return $subTag;
    }
    
    /**
     * @inheritDoc
     */
    public function getAllCategory()
    {
        $collection = $this->_helperData->getFactoryByType('category')->create()->getCollection();
        
        return $this->getAllItem($collection);
    }

    /**
     * @inheritDoc
     */
    public function getCategoryList(SearchCriteriaInterface $searchCriteria)
    {
        $collection = $this->_helperData->getFactoryByType('category')->create()->getCollection();

        return $this->getListEntity($collection, $searchCriteria);
    }

    /**
     * @inheritDoc
     */
    public function getPostsByCategoryId($categoryId)
    {
        $category = $this->_helperData->getFactoryByType('category')->create()->load($categoryId);

        return $category->getSelectedPostsCollection()->getItems();
    }

    /**
     * @inheritDoc
     */
    public function getCategoriesByPostId($postId)
    {
        $post = $this->_helperData->getFactoryByType()->create()->load($postId);

        return $post->getSelectedCategoriesCollection()->getItems();
    }

    /**
     * @param CategoryInterface $category
     *
     * @return CategoryInterface
     */
    public function createCategory($category)
    {
        if (!empty($category->getName())) {
            if (empty($category->getEnabled())) {
                $category->setEnabled(1);
            }
            if (empty($category->getCreatedAt())) {
                $category->setCreatedAt($this->date->date());
            }
            $category->save();
        }

        return $category;
    }

    /**
     * @param string $categoryId
     *
     * @return bool|string
     * @throws Exception
     */
    public function deleteCategory($categoryId)
    {
        $category = $this->_helperData->getFactoryByType('category')->create()->load($categoryId);

        if ($category) {
            $category->delete();

            return true;
        }

        return false;
    }

    /**
     * @inheritDoc
     */
    public function updateCategory($categoryId, $category)
    {
        if (empty($categoryId)) {
            throw new InputException(__('Invalid category id %1', $categoryId));
        }
        $subCategory = $this->_helperData->getFactoryByType('category')->create()->load($categoryId);

        if (!$subCategory->getId()) {
            throw new NoSuchEntityException(
                __(
                    'The "%1" Category doesn\'t exist.',
                    $categoryId
                )
            );
        }

        $subCategory->addData($category->getData())->save();

        return $subCategory;
    }

    /**
     * @param array $data
     */
    protected function prepareData(&$data)
    {
        if (!empty($data['categories_ids'])) {
            $data['categories_ids'] = explode(',', $data['categories_ids']);
        }
        if (!empty($data['tags_ids'])) {
            $data['tags_ids'] = explode(',', $data['tags_ids']);
        }
        if (empty($data['enabled'])) {
            $data['enabled'] = 0;
        }
        if (empty($data['store_ids'])) {
            $data['store_ids'] = 0;
        }
        $data['created_at'] = $this->date->date();
    }

    /**
     * @param array $data
     *
     * @return bool
     */
    protected function checkPostData($data)
    {
        if (empty($data['name'])) {
            return false;
        }

        if (!empty($data['categories_ids'])) {
            $collection = $this->_helperData->getFactoryByType('category')->create()->getCollection();
            foreach (explode(',', $data['categories_ids']) as $id) {
                if ($collection->addFieldToFilter('category_id', $id)->count() < 1) {
                    return false;
                }
            }
        }

        if (!empty($data['tags_ids'])) {
            $collection = $this->_helperData->getFactoryByType('tag')->create()->getCollection();
            foreach (explode(',', $data['tags_ids']) as $id) {
                if ($collection->addFieldToFilter('tag_id', $id)->count() < 1) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * @param SalesAbstractCollection|AbstractCollection $searchResult
     * @param SearchCriteriaInterface $searchCriteria
     *
     * @return mixed
     */
    protected function getListEntity($searchResult, $searchCriteria)
    {
        $this->collectionProcessor->process($searchCriteria, $searchResult);
        $searchResult->setSearchCriteria($searchCriteria);

        return $searchResult;
    }

    /**
     * @param AbstractCollection $collection
     *
     * @return mixed
     */
    protected function getAllItem($collection)
    {
        $page  = $this->_request->getParam('page', 1);
        $limit = $this->_request->getParam('limit', 10);

        $collection->getSelect()->limitPage($page, $limit);

        return $collection->getItems();
    }
}
Also, Add Model/SwatchRepository.php file.
<?php
declare(strict_types=1);
namespace Magelearn\ProductsGrid\Model;

use Magelearn\ProductsGrid\Api\Data\SwatchInterface;
use Magelearn\ProductsGrid\Api\Data\SwatchInterfaceFactory;
use Magelearn\ProductsGrid\Api\SwatchRepositoryInterface;
use Magelearn\ProductsGrid\Api\Data\SwatchSearchResultsInterfaceFactory;
use Magelearn\ProductsGrid\Model\ResourceModel\Swatch as SwatchResource;
use Magelearn\ProductsGrid\Model\ResourceModel\Swatch\CollectionFactory as SwatchCollectionFactory;

use Magento\Store\Model\StoreManagerInterface;

use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Api\DataObjectHelper;
use Magento\Framework\Api\SortOrder;
use Magento\Framework\Exception\CouldNotDeleteException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Reflection\DataObjectProcessor;

class SwatchRepository implements SwatchRepositoryInterface
{
    /**
     * @var SwatchResource
     */
    protected $resource;

    /**
     * @var SwatchInterfaceFactory
     */
    protected $swatchFactory;

    /**
     * @var SwatchCollectionFactory
     */
    protected $swatchCollectionFactory;

    /**
     * @var SwatchSearchResultsInterfaceFactory
     */
    protected $searchResultsFactory;

    /**
     * @var DataObjectHelper
     */
    protected $dataObjectHelper;

    /**
     * @var DataObjectProcessor
     */
    protected $dataObjectProcessor;

    /**
     * @var StoreManagerInterface
     */
    private $storeManager;

    /**
     * @param SwatchResource $resource
     * @param SwatchInterfaceFactory $swatchFactory
     * @param SwatchCollectionFactory $swatchCollectionFactory
     * @param SwatchSearchResultsInterfaceFactory $searchResultsFactory
     * @param DataObjectHelper $dataObjectHelper
     * @param DataObjectProcessor $dataObjectProcessor
     * @param StoreManagerInterface $storeManager
     */
    public function __construct(
        SwatchResource $resource,
        SwatchInterfaceFactory $swatchFactory,
        SwatchCollectionFactory $swatchCollectionFactory,
        SwatchSearchResultsInterfaceFactory $searchResultsFactory,
        DataObjectHelper $dataObjectHelper,
        DataObjectProcessor $dataObjectProcessor,
        StoreManagerInterface $storeManager
    ) {
        $this->resource = $resource;
        $this->swatchFactory = $swatchFactory;
        $this->swatchCollectionFactory = $swatchCollectionFactory;
        $this->searchResultsFactory = $searchResultsFactory;
        $this->dataObjectHelper = $dataObjectHelper;
        $this->dataObjectProcessor = $dataObjectProcessor;
        $this->storeManager = $storeManager;
    }

    /**
     * {@inheritdoc}
     */
    public function save(SwatchInterface $swatch)
    {
        try {
            $this->resource->save($swatch);
        } catch (\Exception $exception) {
            throw new CouldNotSaveException(__(
                'Could not save the swatch: %1',
                $exception->getMessage()
            ));
        }
        return $swatch;
    }

    /**
     * {@inheritdoc}
     */
    public function getById($swatchId)
    {
        $swatch = $this->swatchFactory->create();
        $this->resource->load($swatch, $swatchId);
        if (!$swatch->getId()) {
            throw new NoSuchEntityException(__('swatch with id "%1" does not exist.', $swatchId));
        }
        return $swatch;
    }

    /**
     * {@inheritdoc}
     */
    public function getListByPostId($postId)
    {
        $swatchCollection = $this->swatchCollectionFactory->create();
        $swatchCollection->addFieldToFilter('item_id', $postId);
        $swatchCollection->addOrder('position', 'ASC');
        return $swatchCollection;
    }

    /**
     * {@inheritdoc}
     */
    public function delete(SwatchInterface $swatch)
    {
        try {
            $this->resource->delete($swatch);
        } catch (\Exception $exception) {
            throw new CouldNotDeleteException(__(
                'Could not delete the Swatch: %1',
                $exception->getMessage()
            ));
        }
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function deleteById($swatchId)
    {
        return $this->delete($this->getById($swatchId));
    }
}
Now As per the highlighted code in Model/ItemRepository.php file, we will add Helper/Data.php file.
<?php

namespace Magelearn\ProductsGrid\Helper;

use DateTimeZone;
use Exception;
use Magento\Framework\App\Helper\Context;
use Magento\Framework\DataObject;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\Stdlib\DateTime\DateTime;
use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\App\Helper\AbstractHelper;
use Magelearn\ProductsGrid\Model\Tag;
use Magelearn\ProductsGrid\Model\TagFactory;
use Magelearn\ProductsGrid\Model\Category;
use Magelearn\ProductsGrid\Model\CategoryFactory;
use Magelearn\ProductsGrid\Model\Post;
use Magelearn\ProductsGrid\Model\PostFactory;

/**
 * Class Data
 * @package Magelearn\ProductsGrid\Helper
 */
class Data extends AbstractHelper
{
    const TYPE_POST          = 'post';
    const TYPE_CATEGORY      = 'category';
    const TYPE_TAG           = 'tag';
    
    /**
     * @var PostFactory
     */
    public $postFactory;
    
    /**
     * @var CategoryFactory
     */
    public $categoryFactory;
    
    /**
     * @var TagFactory
     */
    public $tagFactory;

    /**
     * @var DateTime
     */
    public $dateTime;

    /**
     * Data constructor.
     *
     * @param Context $context
     * @param ObjectManagerInterface $objectManager
     * @param StoreManagerInterface $storeManager
     * @param PostFactory $postFactory
     * @param CategoryFactory $categoryFactory
     * @param TagFactory $tagFactory
     * @param DateTime $dateTime
     */
    public function __construct(
        Context $context,
        ObjectManagerInterface $objectManager,
        StoreManagerInterface $storeManager,
        PostFactory $postFactory,
        CategoryFactory $categoryFactory,
        TagFactory $tagFactory,
        DateTime $dateTime
    ) {
        $this->postFactory        = $postFactory;
        $this->categoryFactory    = $categoryFactory;
        $this->tagFactory         = $tagFactory;
        $this->dateTime           = $dateTime;

        parent::__construct($context, $objectManager, $storeManager);
    }

    /**
     * @return Image
     */
    public function getImageHelper()
    {
        return $this->objectManager->get(Image::class);
    }
    
    /**
     * @param null $type
     * @param null $id
     * @param null $storeId
     *
     * @return PostCollection
     * @throws NoSuchEntityException
     */
    public function getPostCollection($type = null, $id = null, $storeId = null)
    {
        if ($id === null) {
            $id = $this->_request->getParam('id');
        }
        
        /** @var PostCollection $collection */
        $collection = $this->getPostList($storeId);
        
        switch ($type) {
            case self::TYPE_CATEGORY:
                $collection->join(
                ['category' => $collection->getTable('magelearn_item_post_category')],
                'main_table.item_id=category.item_id AND category.category_id=' . $id,
                ['position']
                );
                $collection->getSelect()->order('position asc');
                break;
            case self::TYPE_TAG:
                $collection->join(
                ['tag' => $collection->getTable('magelearn_item_post_tag')],
                'main_table.item_id=tag.item_id AND tag.tag_id=' . $id,
                ['position']
                );
                $collection->getSelect()->order('position asc');
                break;            
        }
        
        return $collection;
    }
    
    /**
     * @param null $storeId
     *
     * @return PostCollection
     * @throws NoSuchEntityException
     */
    public function getPostList($storeId = null)
    {
        /** @var PostCollection $collection */
        $collection = $this->getObjectList(self::TYPE_POST, $storeId)
        ->addFieldToFilter('created_at', ['to' => $this->dateTime->date()])
        ->setOrder('item_id', 'desc');
        
        return $collection;
    }
    
    /**
     * Get object collection (Category, Tag, Post)
     *
     * @param null $type
     * @param null $storeId
     *
     * @return CategoryCollection|PostCollection|TagCollection|Collection
     * @throws NoSuchEntityException
     */
    public function getObjectList($type = null, $storeId = null)
    {
        /** @var CategoryCollection|PostCollection|TagCollection|Collection $collection */
        $collection = $this->getFactoryByType($type)
        ->create()
        ->getCollection()
        ->addFieldToFilter('enabled', 1);
        
        $this->addStoreFilter($collection, $storeId);
        
        return $collection;
    }
    
    /**
     * @param $type
     *
     * @return CategoryFactory|PostFactory|TagFactory
     */
    public function getFactoryByType($type = null)
    {
        switch ($type) {
            case self::TYPE_CATEGORY:
                $object = $this->categoryFactory;
                break;
            case self::TYPE_TAG:
                $object = $this->tagFactory;
                break;
            default:
                $object = $this->postFactory;
        }
        
        return $object;
    }
}

Now as per the highlighted code above, we will add our ImageHelper class at Helper/Image.php file.

<?php

namespace Magelearn\ProductsGrid\Helper;

use Magelearn\ProductsGrid\Helper\Media;

/**
 * Class Image
 * @package Magelearn\ProductsGrid\Helper
 */
class Image extends Media
{
    const TEMPLATE_MEDIA_PATH = 'magelearn/productsgrid';
    const TEMPLATE_MEDIA_TYPE_POST = 'post';
}

Also, add our extended class at Helper/Media.php file.

<?php

namespace Magelearn\ProductsGrid\Helper;

use Exception;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\App\Helper\Context;
use Magento\Framework\Exception\FileSystemException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Filesystem;
use Magento\Framework\Filesystem\Directory\WriteInterface;
use Magento\Framework\Image\AdapterFactory;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\UrlInterface;
use Magento\MediaStorage\Model\File\UploaderFactory;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\App\Helper\AbstractHelper;

/**
 * Class Media
 * @package Magelearn\ProductsGrid\Helper
 */
class Media extends AbstractHelper
{
    const TEMPLATE_MEDIA_PATH = 'magelearn';

    /**
     * @var WriteInterface
     */
    protected $mediaDirectory;

    /**
     * @var UploaderFactory
     */
    protected $uploaderFactory;

    /**
     * @var AdapterFactory
     */
    protected $imageFactory;

    /**
     * Media constructor.
     *
     * @param Context $context
     * @param ObjectManagerInterface $objectManager
     * @param StoreManagerInterface $storeManager
     * @param Filesystem $filesystem
     * @param UploaderFactory $uploaderFactory
     * @param AdapterFactory $imageFactory
     *
     * @throws FileSystemException
     */
    public function __construct(
        Context $context,
        ObjectManagerInterface $objectManager,
        StoreManagerInterface $storeManager,
        Filesystem $filesystem,
        UploaderFactory $uploaderFactory,
        AdapterFactory $imageFactory
    ) {
        $this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
        $this->uploaderFactory = $uploaderFactory;
        $this->imageFactory = $imageFactory;

        parent::__construct($context, $objectManager, $storeManager);
    }

    /**
     * @param $data
     * @param string $fileName
     * @param string $type
     * @param null $oldImage
     *
     * @return $this
     */
    public function uploadImage(&$data, $fileName = 'image', $type = '', $oldImage = null)
    {
        if (isset($data[$fileName]['delete']) && $data[$fileName]['delete']) {
            if ($oldImage) {
                try {
                    $this->removeImage($oldImage, $type);
                } catch (Exception $e) {
                    $this->_logger->critical($e->getMessage());
                }
            }
            $data[$fileName] = '';
        } else {
            try {
                $uploader = $this->uploaderFactory->create(['fileId' => $fileName]);
                $uploader->setAllowedExtensions(['jpg', 'jpeg', 'gif', 'png', 'svg']);
                $uploader->setAllowRenameFiles(true);
                $uploader->setFilesDispersion(true);
                $uploader->setAllowCreateFolders(true);

                $path = $this->getBaseMediaPath($type);

                $image = $uploader->save(
                    $this->mediaDirectory->getAbsolutePath($path)
                );

                if ($oldImage) {
                    $this->removeImage($oldImage, $type);
                }

                $data[$fileName] = $this->_prepareFile($image['file']);
            } catch (Exception $e) {
                $data[$fileName] = isset($data[$fileName]['value']) ? $data[$fileName]['value'] : '';
            }
        }

        return $this;
    }

    /**
     * @param $file
     * @param $type
     *
     * @return $this
     * @throws FileSystemException
     */
    public function removeImage($file, $type)
    {
        $image = $this->getMediaPath($file, $type);
        if ($this->mediaDirectory->isFile($image)) {
            $this->mediaDirectory->delete($image);
        }

        return $this;
    }

    /**
     * @param $file
     * @param string $type
     *
     * @return string
     */
    public function getMediaPath($file, $type = '')
    {
        return $this->getBaseMediaPath($type) . '/' . $this->_prepareFile($file);
    }

    /**
     * @param string $type
     *
     * @return string
     */
    public function getBaseMediaPath($type = '')
    {
        return trim(static::TEMPLATE_MEDIA_PATH . '/' . $type, '/');
    }
    
    /**
     * @param string $file
     *
     * @return string
     */
    protected function _prepareFile($file)
    {
        return ltrim(str_replace('\\', '/', $file), '/');
    }
    
    /**
     * @param $file
     *
     * @return string
     * @throws NoSuchEntityException
     */
    public function getMediaUrl($file)
    {
        return $this->getBaseMediaUrl() . '/' . $this->_prepareFile($file);
    }
    
    /**
     * @return string
     * @throws NoSuchEntityException
     */
    public function getBaseMediaUrl()
    {
        return rtrim($this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_MEDIA), '/');
    }

    /**
     * @param $path
     *
     * @return $this
     * @throws FileSystemException
     */
    public function removePath($path)
    {
        $pathMedia = $this->mediaDirectory->getRelativePath($path);
        if ($this->mediaDirectory->isDirectory($pathMedia)) {
            $this->mediaDirectory->delete($path);
        }

        return $this;
    }
}

Now we will add Model/Post.php file.

<?php

namespace Magelearn\ProductsGrid\Model;

use Exception;
use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory;
use Magento\Framework\Data\Collection\AbstractDb;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Model\AbstractModel;
use Magento\Framework\Model\Context;
use Magento\Framework\Model\ResourceModel\AbstractResource;
use Magento\Framework\Registry;
use Magento\Framework\Stdlib\DateTime;
use Magelearn\ProductsGrid\Helper\Data;
use Magelearn\ProductsGrid\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory;
use Magelearn\ProductsGrid\Model\ResourceModel\Post as PostResource;
use Magelearn\ProductsGrid\Model\ResourceModel\Post\Collection;
use Magelearn\ProductsGrid\Model\ResourceModel\Post\CollectionFactory as PostCollectionFactory;
use Magelearn\ProductsGrid\Model\ResourceModel\Tag\CollectionFactory;

/**
 * @method Post setName($name)
 * @method Post setShortDescription($shortDescription)
 * @method Post setItemContent($postContent)
 * @method Post setImage($image)
 * @method Post setEnabled($enabled)
 * @method mixed getName()
 * @method mixed getItemContent()
 * @method mixed getImage()
 * @method mixed getEnabled()
 * @method Post setCreatedAt(string $createdAt)
 * @method string getCreatedAt()
 * @method Post setUpdatedAt(string $updatedAt)
 * @method string getUpdatedAt()
 * @method Post setTagsData(array $data)
 * @method Post setProductsData(array $data)
 * @method array getTagsData()
 * @method array getProductsData()
 * @method Post setIsChangedTagList(bool $flag)
 * @method Post setIsChangedProductList(bool $flag)
 * @method Post setIsChangedCategoryList(bool $flag)
 * @method bool getIsChangedTagList()
 * @method bool getIsChangedCategoryList()
 * @method Post setAffectedTagIds(array $ids)
 * @method Post setAffectedEntityIds(array $ids)
 * @method Post setAffectedCategoryIds(array $ids)
 * @method bool getAffectedTagIds()
 * @method bool getAffectedCategoryIds()
 * @method array getCategoriesIds()
 * @method Post setCategoriesIds(array $categoryIds)
 * @method array getTagsIds()
 * @method Post setTagsIds(array $tagIds)
 * @method mixed getItemTitleColor()
 * @method Post setItemTitleColor($itemTitleColor)
 */
class Post extends AbstractModel
{
    /**
     * Cache tag
     *
     * @var string
     */
    const CACHE_TAG = 'magelearn_item_post';

    /**
     * Cache tag
     *
     * @var string
     */
    protected $_cacheTag = 'magelearn_item_post';

    /**
     * Event prefix
     *
     * @var string
     */
    protected $_eventPrefix = 'magelearn_item_post';

    /**
     * Tag Collection
     *
     * @var ResourceModel\Tag\Collection
     */
    public $tagCollection;

    /**
     * Category Collection
     *
     * @var ResourceModel\Category\Collection
     */
    public $categoryCollection;

    /**
     * Tag Collection Factory
     *
     * @var CollectionFactory
     */
    public $tagCollectionFactory;

    /**
     * Post Category Collection Factory
     *
     * @var CategoryCollectionFactory
     */
    public $categoryCollectionFactory;

    /**
     * Post Collection Factory
     *
     * @var PostCollectionFactory
     */
    public $postCollectionFactory;

    /**
     * @var DateTime
     */
    public $dateTime;

    /**
     * @var Data
     */
    public $helperData;

    /**
     * @var ProductCollectionFactory
     */
    public $productCollectionFactory;

    /**
     * @var ProductCollection
     */
    public $productCollection;

    /**
     * Post constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param DateTime $dateTime
     * @param Data $helperData
     * @param CollectionFactory $tagCollectionFactory
     * @param CategoryCollectionFactory $categoryCollectionFactory
     * @param PostCollectionFactory $postCollectionFactory
     * @param ProductCollectionFactory $productCollectionFactory
     * @param AbstractResource|null $resource
     * @param AbstractDb|null $resourceCollection
     * @param array $data
     */
    public function __construct(
        Context $context,
        Registry $registry,
        DateTime $dateTime,
        Data $helperData,
        CollectionFactory $tagCollectionFactory,
        CategoryCollectionFactory $categoryCollectionFactory,
        PostCollectionFactory $postCollectionFactory,
        ProductCollectionFactory $productCollectionFactory,
        AbstractResource $resource = null,
        AbstractDb $resourceCollection = null,
        array $data = []
    ) {
        $this->tagCollectionFactory      = $tagCollectionFactory;
        $this->categoryCollectionFactory = $categoryCollectionFactory;
        $this->postCollectionFactory     = $postCollectionFactory;
        $this->productCollectionFactory  = $productCollectionFactory;
        $this->helperData                = $helperData;
        $this->dateTime                  = $dateTime;

        parent::__construct($context, $registry, $resource, $resourceCollection, $data);
    }

    /**
     * Initialize resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init(PostResource::class);
    }

    /**
     * @param bool $shorten
     *
     * @return mixed|string
     */
    public function getShortDescription($shorten = false)
    {
        $shortDescription = $this->getData('short_description');

        $maxLength = 200;
        if ($shorten && strlen($shortDescription) > $maxLength) {
            $shortDescription = substr($shortDescription, 0, $maxLength) . '...';
        }

        return $shortDescription;
    }

    /**
     * Get identities
     *
     * @return array
     */
    public function getIdentities()
    {
        return [self::CACHE_TAG . '_' . $this->getId()];
    }

    /**
     * get entity default values
     *
     * @return array
     */
    public function getDefaultValues()
    {
        $values                  = [];
        $values['enabled']       = '1';
        $values['store_ids']     = '1';

        return $values;
    }

    /**
     * @return ResourceModel\Tag\Collection
     */
    public function getSelectedTagsCollection()
    {
        if ($this->tagCollection === null) {
            $collection = $this->tagCollectionFactory->create();
            $collection->getSelect()->join(
                $this->getResource()->getTable('magelearn_item_post_tag'),
                'main_table.tag_id=' . $this->getResource()->getTable('magelearn_item_post_tag') . '.tag_id AND '
                . $this->getResource()->getTable('magelearn_item_post_tag') . '.item_id=' . $this->getId(),
                ['position']
            )->where("main_table.enabled='1'");
            $this->tagCollection = $collection;
        }

        return $this->tagCollection;
    }

    /**
     * @return ResourceModel\Category\Collection
     */
    public function getSelectedCategoriesCollection()
    {
        if ($this->categoryCollection === null) {
            $collection = $this->categoryCollectionFactory->create();
            $collection->join(
                $this->getResource()->getTable('magelearn_item_post_category'),
                'main_table.category_id=' . $this->getResource()->getTable('magelearn_item_post_category') .
                '.category_id AND ' . $this->getResource()->getTable('magelearn_item_post_category') . '.item_id="'
                . $this->getId() . '"',
                ['position']
            );
            $this->categoryCollection = $collection;
        }

        return $this->categoryCollection;
    }

    /**
     * @return array
     * @throws LocalizedException
     */
    public function getCategoryIds()
    {
        if (!$this->hasData('category_ids')) {
            $ids = $this->_getResource()->getCategoryIds($this);
            $this->setData('category_ids', $ids);
        }

        return (array) $this->_getData('category_ids');
    }

    /**
     * @return array
     * @throws LocalizedException
     */
    public function getTagIds()
    {
        if (!$this->hasData('tag_ids')) {
            $ids = $this->_getResource()->getTagIds($this);

            $this->setData('tag_ids', $ids);
        }

        return (array) $this->_getData('tag_ids');
    }

    /**
     * @return mixed
     * @throws NoSuchEntityException
     */
    public function getUrlImage()
    {
        $imageHelper = $this->helperData->getImageHelper();
        $imageFile   = $this->getImage() ? $imageHelper->getMediaPath($this->getImage(), 'post') : '';
        $imageUrl    = $imageFile ? $this->helperData->getImageHelper()->getMediaUrl($imageFile) : '';

        $this->setData('image', $imageUrl);

        return $this->_getData('image');
    }

    /**
     * @return ProductCollection
     */
    public function getSelectedProductsCollection()
    {
        if ($this->productCollection === null) {
            $collection = $this->productCollectionFactory->create();
            $collection->getSelect()->join(
                $this->getResource()->getTable('magelearn_item_post_product'),
                'e.entity_id=' . $this->getResource()->getTable('magelearn_item_post_product')
                . '.entity_id AND ' . $this->getResource()->getTable('magelearn_item_post_product') . '.item_id='
                . $this->getId(),
                ['position']
            );
            $this->productCollection = $collection;
        }

        return $this->productCollection;
    }

    /**
     * @return array|mixed
     */
    public function getProductsPosition()
    {
        if (!$this->getId()) {
            return [];
        }
        $array = $this->getData('products_position');
        if ($array === null) {
            $array = $this->getResource()->getProductsPosition($this);
            $this->setData('products_position', $array);
        }

        return $array;
    }
}

Add Model/Category.php file.

<?php

namespace Magelearn\ProductsGrid\Model;

use Exception;
use Magento\Framework\Data\Collection\AbstractDb;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Model\AbstractModel;
use Magento\Framework\Model\Context;
use Magento\Framework\Model\ResourceModel\AbstractResource;
use Magento\Framework\Registry;
use Magelearn\ProductsGrid\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory;
use Magelearn\ProductsGrid\Model\ResourceModel\Post\Collection;
use Magelearn\ProductsGrid\Model\ResourceModel\Post\CollectionFactory;

/**
 * @method Category setName($name)
 * @method Category setEnabled($enabled)
 * @method mixed getName()
 * @method mixed getEnabled()
 * @method Category setCreatedAt(string $createdAt)
 * @method string getCreatedAt()
 * @method Category setUpdatedAt(string $updatedAt)
 * @method string getUpdatedAt()
 * @method Category setAffectedCategoryIds(array $ids)
 * @method Category setPostsData(array $data)
 * @method array getPostsData()
 * @method Category setIsChangedPostList(bool $flag)
 * @method bool getIsChangedPostList()
 * @method Category setAffectedPostIds(array $ids)
 * @method bool getAffectedPostIds()
 */
class Category extends AbstractModel
{
    /**
     * Cache tag
     *
     * @var string
     */
    const CACHE_TAG = 'magelearn_item_category';

    /**
     * Cache tag
     *
     * @var string
     */
    protected $_cacheTag = 'magelearn_item_category';

    /**
     * Event prefix
     *
     * @var string
     */
    protected $_eventPrefix = 'magelearn_item_category';

    /**
     * Post Collection
     *
     * @var Collection
     */
    public $postCollection;

    /**
     * Post Category Factory
     *
     * @var CategoryFactory
     */
    public $categoryFactory;

    /**
     * Post Collection Factory
     *
     * @var CollectionFactory
     */
    public $postCollectionFactory;

    /**
     * @var CategoryCollectionFactory
     */
    public $categoryCollectionFactory;

    /**
     * Category constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param CategoryFactory $categoryFactory
     * @param CollectionFactory $postCollectionFactory
     * @param CategoryCollectionFactory $categoryCollectionFactory
     * @param AbstractResource|null $resource
     * @param AbstractDb|null $resourceCollection
     * @param array $data
     */
    public function __construct(
        Context $context,
        Registry $registry,
        CategoryFactory $categoryFactory,
        CollectionFactory $postCollectionFactory,
        CategoryCollectionFactory $categoryCollectionFactory,
        AbstractResource $resource = null,
        AbstractDb $resourceCollection = null,
        array $data = []
    ) {
        $this->categoryFactory = $categoryFactory;
        $this->postCollectionFactory = $postCollectionFactory;
        $this->categoryCollectionFactory = $categoryCollectionFactory;

        parent::__construct($context, $registry, $resource, $resourceCollection, $data);
    }

    /**
     * Initialize resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init(ResourceModel\Category::class);
    }

    /**
     * Get identities
     *
     * @return array
     */
    public function getIdentities()
    {
        return [self::CACHE_TAG . '_' . $this->getId()];
    }
    
    /**
     * get entity default values
     *
     * @return array
     */
    public function getDefaultValues()
    {
        $values = [];
        $values['enabled'] = '1';
        
        return $values;
    }

    /**
     * @return array|mixed
     */
    public function getPostsPosition()
    {
        if (!$this->getId()) {
            return [];
        }
        $array = $this->getData('posts_position');
        if ($array === null) {
            $array = $this->getResource()->getPostsPosition($this);
            $this->setData('posts_position', $array);
        }

        return $array;
    }

    /**
     * @return Collection
     */
    public function getSelectedPostsCollection()
    {
        if (!$this->postCollection) {
            $collection = $this->postCollectionFactory->create();
            $collection->join(
                ['cat' => $this->getResource()->getTable('magelearn_item_post_category')],
                'main_table.item_id=cat.item_id AND cat.category_id=' . $this->getId(),
                ['position']
            );
            $collection->setOrder('item_id','DESC');
            $this->postCollection = $collection;
        }

        return $this->postCollection;
    }
}

Add Model/Tag.php file.

<?php

namespace Magelearn\ProductsGrid\Model;

use Magento\Framework\Data\Collection\AbstractDb;
use Magento\Framework\Model\AbstractModel;
use Magento\Framework\Model\Context;
use Magento\Framework\Model\ResourceModel\AbstractResource;
use Magento\Framework\Registry;
use Magelearn\ProductsGrid\Model\ResourceModel\Post\Collection;
use Magelearn\ProductsGrid\Model\ResourceModel\Post\CollectionFactory;
use Magelearn\ProductsGrid\Model\ResourceModel\Tag\CollectionFactory as TagCollectionFactory;

/**
 * @method Tag setName($name)
 * @method Tag setDescription($description)
 * @method Tag setEnabled($enabled)
 * @method mixed getName()
 * @method mixed getDescription()
 * @method mixed getEnabled()
 * @method Tag setCreatedAt(string $createdAt)
 * @method string getCreatedAt()
 * @method Tag setUpdatedAt(string $updatedAt)
 * @method string getUpdatedAt()
 * @method Tag setPostsData(array $data)
 * @method array getPostsData()
 * @method Tag setIsChangedPostList(bool $flag)
 * @method bool getIsChangedPostList()
 * @method Tag setAffectedPostIds(array $ids)
 * @method bool getAffectedPostIds()
 */
class Tag extends AbstractModel
{
    /**
     * Cache tag
     *
     * @var string
     */
    const CACHE_TAG = 'magelearn_item_tag';

    /**
     * Cache tag
     *
     * @var string
     */
    protected $_cacheTag = 'magelearn_item_tag';

    /**
     * Event prefix
     *
     * @var string
     */
    protected $_eventPrefix = 'magelearn_item_tag';

    /**
     * Post Collection
     *
     * @var Collection
     */
    public $postCollection;

    /**
     * Post Collection Factory
     *
     * @var CollectionFactory
     */
    public $postCollectionFactory;

    /**
     * @var TagCollectionFactory
     */
    public $tagCollectionFactory;

    /**
     * Tag constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param CollectionFactory $postCollectionFactory
     * @param TagCollectionFactory $tagCollectionFactory
     * @param AbstractResource|null $resource
     * @param AbstractDb|null $resourceCollection
     * @param array $data
     */
    public function __construct(
        Context $context,
        Registry $registry,
        CollectionFactory $postCollectionFactory,
        TagCollectionFactory $tagCollectionFactory,
        AbstractResource $resource = null,
        AbstractDb $resourceCollection = null,
        array $data = []
    ) {
        $this->postCollectionFactory = $postCollectionFactory;
        $this->tagCollectionFactory = $tagCollectionFactory;

        parent::__construct($context, $registry, $resource, $resourceCollection, $data);
    }

    /**
     * Initialize resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init(ResourceModel\Tag::class);
    }

    /**
     * Get identities
     *
     * @return array
     */
    public function getIdentities()
    {
        return [self::CACHE_TAG . '_' . $this->getId()];
    }

    /**
     * @return array|mixed
     */
    public function getPostsPosition()
    {
        if (!$this->getId()) {
            return [];
        }

        $array = $this->getData('posts_position');
        if (!$array) {
            $array = $this->getResource()->getPostsPosition($this);
            $this->setData('posts_position', $array);
        }

        return $array;
    }

    /**
     * @return Collection
     */
    public function getSelectedPostsCollection()
    {
        if ($this->postCollection === null) {
            $collection = $this->postCollectionFactory->create();
            $collection->join(
                ['post_tag' => $this->getResource()->getTable('magelearn_item_post_tag')],
                'main_table.item_id=post_tag.item_id AND post_tag.tag_id=' . $this->getId(),
                ['position']
            );

            $this->postCollection = $collection;
        }

        return $this->postCollection;
    }
}

Also, add Model/Swatch.php file.

<?php

declare(strict_types=1);

namespace Magelearn\ProductsGrid\Model;

use Magento\Framework\Data\Collection\AbstractDb;
use Magento\Framework\Model\AbstractModel;
use Magento\Framework\Model\Context;
use Magento\Framework\Model\ResourceModel\AbstractResource;
use Magento\Framework\Registry;
use Magelearn\ProductsGrid\Api\Data\SwatchInterface;
use Magelearn\ProductsGrid\Model\ResourceModel\Post\Collection;
use Magelearn\ProductsGrid\Model\ResourceModel\Post\CollectionFactory;

class Swatch extends AbstractModel implements SwatchInterface
{
    /**
     * Cache tag
     *
     * @var string
     */
    const CACHE_TAG = 'magelearn_item_swatch';
    
    /**
     * Cache tag
     *
     * @var string
     */
    protected $_cacheTag = 'magelearn_item_swatch';
    /**
     * {@inheritdoc}
     */
    protected $_eventPrefix = 'magelearn_item_swatch';
    
    /**
     * Post Collection
     *
     * @var Collection
     */
    public $postCollection;
    
    /**
     * Post Collection Factory
     *
     * @var CollectionFactory
     */
    public $postCollectionFactory;
    
    /**
     * Swatch constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param CollectionFactory $postCollectionFactory
     * @param AbstractResource|null $resource
     * @param AbstractDb|null $resourceCollection
     * @param array $data
     */
    public function __construct(
        Context $context,
        Registry $registry,
        CollectionFactory $postCollectionFactory,
        AbstractResource $resource = null,
        AbstractDb $resourceCollection = null,
        array $data = []
        ) {
            $this->postCollectionFactory = $postCollectionFactory;
            
            parent::__construct($context, $registry, $resource, $resourceCollection, $data);
    }

    /**
     * Initialize resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init(\Magelearn\ProductsGrid\Model\ResourceModel\Swatch::class);
    }

    /**
     * Get identities
     *
     * @return array
     */
    public function getIdentities()
    {
        return [self::CACHE_TAG . '_' . $this->getId()];
    }
    
    /**
     * {@inheritdoc}
     */
    public function getSwatchId()
    {
        return $this->getData(self::SWATCH_ID);
    }
    
    /**
     * {@inheritdoc}
     */
    public function setSwatchId(int $swatchId): SwatchInterface
    {
        return $this->setData(self::SWATCH_ID, $swatchId);
    }
    
    /**
     * Get description
     * @return string|NULL
     */
    public function getDescription(): ?string
    {
        return $this->getData(self::DESCRIPTION);
    }
    
    /**
     * {@inheritdoc}
     */
    public function setDescription(?string $description): SwatchInterface
    {
        return $this->setData(self::DESCRIPTION, $description);
    }
    
    /**
     * {@inheritdoc}
     */
    public function getPosition(): int
    {
        return (int)$this->getData(self::POSITION);
    }
    
    /**
     * {@inheritdoc}
     */
    public function setPosition(int $position): SwatchInterface
    {
        return $this->setData(self::POSITION, $position);
    }
    
    /**
     * {@inheritdoc}
     */
    public function getItemId(): int
    {
        return $this->getData(self::ITEM_ID);
    }
    
    /**
     * {@inheritdoc}
     */
    public function setItemId(int $itemId): SwatchInterface
    {
        return $this->setData(self::ITEM_ID, $itemId);
    }
    
    /**
     * {@inheritdoc}
     */
    public function getImage()
    {
        return $this->getData(self::IMAGE);
    }
    
    /**
     * {@inheritdoc}
     */
    public function setImage(string $image): SwatchInterface
    {
        return $this->setData(self::IMAGE, $image);
    }
}

Now we will add Model/ResourceModel/Post.php file.

<?php

namespace Magelearn\ProductsGrid\Model\ResourceModel;

use Magento\Backend\Model\Auth;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Event\ManagerInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Model\AbstractModel;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
use Magento\Framework\Model\ResourceModel\Db\Context;
use Magento\Framework\Stdlib\DateTime\DateTime;
use Magelearn\ProductsGrid\Helper\Data;
use Magelearn\ProductsGrid\Model\Post as PostModel;

/**
 * Class Post
 * @package Magelearn\ProductsGrid\Model\ResourceModel
 */
class Post extends AbstractDb
{
    /**
     * Date model
     *
     * @var DateTime
     */
    public $date;

    /**
     * Event Manager
     *
     * @var ManagerInterface
     */
    public $eventManager;

    /**
     * Tag relation model
     *
     * @var string
     */
    public $postTagTable;

    /**
     * Item Category relation model
     *
     * @var string
     */
    public $postCategoryTable;

    /**
     * @var string
     */
    public $postProductTable;

    /**
     * @var Data
     */
    public $helperData;

    /**
     * @var Auth
     */
    protected $_auth;

    /**
     * @var RequestInterface
     */
    protected $_request;

    /**
     * Post constructor.
     *
     * @param Context $context
     * @param DateTime $date
     * @param ManagerInterface $eventManager
     * @param Auth $auth
     * @param Data $helperData
     * @param RequestInterface $request
     */
    public function __construct(
        Context $context,
        DateTime $date,
        ManagerInterface $eventManager,
        Auth $auth,
        Data $helperData,
        RequestInterface $request
    ) {
        $this->date           = $date;
        $this->eventManager   = $eventManager;
        $this->_auth          = $auth;
        $this->helperData     = $helperData;
        $this->_request       = $request;

        parent::__construct($context);

        $this->postTagTable      = $this->getTable('magelearn_item_post_tag');
        $this->postCategoryTable = $this->getTable('magelearn_item_post_category');
        $this->postProductTable  = $this->getTable('magelearn_item_post_product');
    }

    /**
     * Initialize resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init('magelearn_item_post', 'item_id');
    }

    /**
     * Retrieves Post Name from DB by passed id.
     *
     * @param int $id
     *
     * @return string
     * @throws LocalizedException
     */
    public function getPostNameById($id)
    {
        $adapter = $this->getConnection();
        $select  = $adapter->select()
            ->from($this->getMainTable(), 'name')
            ->where('item_id = :item_id');
        $binds   = ['item_id' => (int) $id];

        return $adapter->fetchOne($select, $binds);
    }

    /**
     * before save callback
     *
     * @param AbstractModel $object
     *
     * @return $this
     * @throws LocalizedException
     */
    protected function _beforeSave(AbstractModel $object)
    {
        if (is_array($object->getStoreIds())) {
            $object->setStoreIds(implode(',', $object->getStoreIds()));
        }

        return $this;
    }

    /**
     * @param PostModel|AbstractModel $object
     * @return AbstractDb
     * @throws LocalizedException
     */
    protected function _afterSave(AbstractModel $object)
    {
        $this->saveTagRelation($object);
        $this->saveCategoryRelation($object);
        $this->saveProductRelation($object);

        return parent::_afterSave($object);
    }

    /**
     * @param PostModel $post
     *
     * @return $this
     * @throws LocalizedException
     */
    public function saveTagRelation(PostModel $post)
    {
        $post->setIsChangedTagList(false);
        $id      = $post->getId();
        $tags    = $post->getTagsIds();
        $oldTags = $post->getTagIds();

        if ($tags === null) {
            return $this;
        }

        $insert  = array_diff($tags, $oldTags);
        $delete  = array_diff($oldTags, $tags);
        $adapter = $this->getConnection();
        if (!empty($delete)) {
            $condition = ['tag_id IN(?)' => $delete, 'item_id=?' => $id];
            $adapter->delete($this->postTagTable, $condition);
        }
        if (!empty($insert)) {
            $data = [];
            foreach ($insert as $tagId) {
                $data[] = [
                    'item_id'  => (int) $id,
                    'tag_id'   => (int) $tagId,
                    'position' => 1
                ];
            }
            $adapter->insertMultiple($this->postTagTable, $data);
        }
        if (!empty($insert) || !empty($delete)) {
            $tagIds = array_unique(array_merge(array_keys($insert), array_keys($delete)));
            $this->eventManager->dispatch(
                'magelearn_item_post_change_tags',
                ['post' => $post, 'tag_ids' => $tagIds]
            );
        }

        if (!empty($insert) || !empty($delete)) {
            $post->setIsChangedTagList(true);
            $tagIds = array_keys($insert + $delete);
            $post->setAffectedTagIds($tagIds);
        }

        return $this;
    }

    /**
     * @param PostModel $post
     *
     * @return $this
     * @throws LocalizedException
     */
    public function saveCategoryRelation(PostModel $post)
    {
        $post->setIsChangedCategoryList(false);
        $id             = $post->getId();
        $categories     = $post->getCategoriesIds();
        $oldCategoryIds = $post->getCategoryIds();

        if ($categories === null) {
            return $this;
        }

        $insert         = array_diff($categories, $oldCategoryIds);
        $delete         = array_diff($oldCategoryIds, $categories);
        $adapter        = $this->getConnection();

        if (!empty($delete)) {
            $condition = ['category_id IN(?)' => $delete, 'item_id=?' => $id];
            $adapter->delete($this->postCategoryTable, $condition);
        }
        if (!empty($insert)) {
            $data = [];
            foreach ($insert as $categoryId) {
                $data[] = [
                    'item_id'     => (int) $id,
                    'category_id' => (int) $categoryId,
                    'position'    => 1
                ];
            }
            $adapter->insertMultiple($this->postCategoryTable, $data);
        }
        if (!empty($insert) || !empty($delete)) {
            $categoryIds = array_unique(array_merge(array_keys($insert), array_keys($delete)));
            $this->eventManager->dispatch(
                'magelearn_item_post_change_categories',
                ['post' => $post, 'category_ids' => $categoryIds]
            );
        }
        if (!empty($insert) || !empty($delete)) {
            $post->setIsChangedCategoryList(true);
            $categoryIds = array_keys($insert + $delete);
            $post->setAffectedCategoryIds($categoryIds);
        }

        return $this;
    }

    /**
     * @param PostModel $post
     *
     * @return array
     */
    public function getCategoryIds(PostModel $post)
    {
        $adapter = $this->getConnection();
        $select  = $adapter->select()->from(
            $this->postCategoryTable,
            'category_id'
        )
            ->where(
                'item_id = ?',
                (int) $post->getId()
            );

        return $adapter->fetchCol($select);
    }

    /**
     * @param PostModel $post
     *
     * @return array
     */
    public function getTagIds(PostModel $post)
    {
        $adapter = $this->getConnection();
        $select  = $adapter->select()->from(
            $this->postTagTable,
            'tag_id'
        )
            ->where(
                'item_id = ?',
                (int) $post->getId()
            );

        return $adapter->fetchCol($select);
    }

    /**
     * @param PostModel $post
     *
     * @return $this
     */
    public function saveProductRelation(PostModel $post)
    {
        $post->setIsChangedProductList(false);
        $id          = $post->getId();
        $products    = $post->getProductsData();
        $oldProducts = $post->getProductsPosition();

        if (is_array($products)) {
            $insert  = array_diff_key($products, $oldProducts);
            $delete  = array_diff_key($oldProducts, $products);
            $update  = array_intersect_key($products, $oldProducts);
            $_update = [];
            foreach ($update as $key => $settings) {
                if (isset($oldProducts[$key]) && $oldProducts[$key] != $settings['position']) {
                    $_update[$key] = $settings;
                }
            }
            $update = $_update;
        }
        $adapter = $this->getConnection();
        if ($products === null && $this->_request->getActionName() === 'save') {
            foreach (array_keys($oldProducts) as $value) {
                $condition = ['entity_id =?' => (int) $value, 'item_id=?' => (int) $id];
                $adapter->delete($this->postProductTable, $condition);
            }

            return $this;
        }
        if (!empty($delete)) {
            foreach (array_keys($delete) as $value) {
                $condition = ['entity_id =?' => (int) $value, 'item_id=?' => (int) $id];
                $adapter->delete($this->postProductTable, $condition);
            }
        }
        if (!empty($insert)) {
            $data = [];
            foreach ($insert as $entityId => $position) {
                $data[] = [
                    'item_id'   => (int) $id,
                    'entity_id' => (int) $entityId,
                    'position'  => (int) $position['position']
                ];
            }
            $adapter->insertMultiple($this->postProductTable, $data);
        }
        if (!empty($update)) {
            foreach ($update as $entityId => $position) {
                $where = ['item_id = ?' => (int) $id, 'entity_id = ?' => (int) $entityId];
                $bind  = ['position' => (int) $position['position']];
                $adapter->update($this->postProductTable, $bind, $where);
            }
        }
        if (!empty($insert) || !empty($delete)) {
            $entityIds = array_unique(array_merge(array_keys($insert), array_keys($delete)));
            $this->eventManager->dispatch(
                'magelearn_item_post_change_products',
                ['post' => $post, 'entity_ids' => $entityIds]
            );
        }
        if (!empty($insert) || !empty($update) || !empty($delete)) {
            $post->setIsChangedProductList(true);
            $entityIds = array_keys($insert + $delete + $update);
            $post->setAffectedEntityIds($entityIds);
        }

        return $this;
    }

    /**
     * @param PostModel $post
     *
     * @return array
     */
    public function getProductsPosition(PostModel $post)
    {
        $select = $this->getConnection()->select()->from(
            $this->postProductTable,
            ['entity_id', 'position']
        )
            ->where(
                'item_id = :item_id'
            );
        $bind   = ['item_id' => (int) $post->getId()];

        return $this->getConnection()->fetchPairs($select, $bind);
    }
    
}

Also, add Model/ResourceModel/Category.php file.

<?php

namespace Magelearn\ProductsGrid\Model\ResourceModel;

use Magento\Framework\DataObject;
use Magento\Framework\Event\ManagerInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Model\AbstractModel;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
use Magento\Framework\Model\ResourceModel\Db\Context;
use Magento\Framework\Stdlib\DateTime\DateTime;
use Magelearn\ProductsGrid\Helper\Data;
use Magelearn\ProductsGrid\Model\Category as CategoryModel;
use Zend_Db_Expr;

/**
 * Class Category
 * @package Magelearn\ProductsGrid\Model\ResourceModel
 */
class Category extends AbstractDb
{
    /**
     * Date model
     *
     * @var DateTime
     */
    public $date;

    /**
     * Event Manager
     *
     * @var ManagerInterface
     */
    public $eventManager;

    /**
     * Post relation model
     *
     * @var string
     */
    public $categoryPostTable;

    /**
     * @var Data
     */
    public $helperData;

    /**
     * Category constructor.
     *
     * @param Data $helperData
     * @param DateTime $date
     * @param ManagerInterface $eventManager
     * @param Context $context
     */
    public function __construct(
        Context $context,
        DateTime $date,
        ManagerInterface $eventManager,
        Data $helperData
    ) {
        $this->helperData   = $helperData;
        $this->date         = $date;
        $this->eventManager = $eventManager;

        parent::__construct($context);

        $this->categoryPostTable = $this->getTable('magelearn_item_post_category');
    }

    /**
     * Initialize resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init('magelearn_item_category', 'category_id');
    }

    /**
     * Retrieves Post Category Name from DB by passed id.
     *
     * @param int $id
     *
     * @return string
     * @throws LocalizedException
     */
    public function getCategoryNameById($id)
    {
        $adapter = $this->getConnection();
        $select  = $adapter->select()
            ->from($this->getMainTable(), 'name')
            ->where('category_id = :category_id');
        $binds   = ['category_id' => (int) $id];

        return $adapter->fetchOne($select, $binds);
    }

    /**
     * Before save call back
     *
     * @param AbstractModel $object
     *
     * @return $this
     * @throws LocalizedException
     */
    protected function _beforeSave(AbstractModel $object)
    {
        $object->setUpdatedAt($this->date->date());
        if ($object->isObjectNew()) {
            $object->setCreatedAt($this->date->date());
        }
        /** @var CategoryModel $object */
        parent::_beforeSave($object);

        if (is_array($object->getStoreIds())) {
            $object->setStoreIds(implode(',', $object->getStoreIds()));
        }

        return $this;
    }

    /**
     * @param AbstractModel $object
     *
     * @return AbstractDb
     * @throws LocalizedException
     */
    protected function _afterSave(AbstractModel $object)
    {
        /** @var CategoryModel $object */
        $this->savePostRelation($object);

        return parent::_afterSave($object);
    }

    /**
     * @param CategoryModel $category
     *
     * @return array
     */
    public function getPostsPosition(CategoryModel $category)
    {
        $select = $this->getConnection()->select()->from(
            $this->categoryPostTable,
            ['item_id', 'position']
        )
            ->where(
                'category_id = :category_id'
            );
        $bind   = ['category_id' => (int) $category->getId()];

        return $this->getConnection()->fetchPairs($select, $bind);
    }

    /**
     * @param CategoryModel $category
     *
     * @return $this
     */
    public function savePostRelation(CategoryModel $category)
    {
        $category->setIsChangedPostList(false);
        $id    = $category->getId();
        $posts = $category->getPostsData();
        if ($posts === null) {
            return $this;
        }
        $oldPosts = $category->getPostsPosition();
        $insert   = array_diff_key($posts, $oldPosts);
        $delete   = array_diff_key($oldPosts, $posts);
        $update   = array_intersect_key($posts, $oldPosts);
        $_update  = [];
        foreach ($update as $key => $position) {
            if (isset($oldPosts[$key]) && $oldPosts[$key] != $position) {
                $_update[$key] = $position;
            }
        }
        $update  = $_update;
        $adapter = $this->getConnection();
        if (!empty($delete)) {
            $condition = ['item_id IN(?)' => array_keys($delete), 'category_id=?' => $id];
            $adapter->delete($this->categoryPostTable, $condition);
        }
        if (!empty($insert)) {
            $data = [];
            foreach ($insert as $postId => $position) {
                $data[] = [
                    'category_id' => (int) $id,
                    'item_id'     => (int) $postId,
                    'position'    => (int) $position
                ];
            }
            $adapter->insertMultiple($this->categoryPostTable, $data);
        }
        if (!empty($update)) {
            foreach ($update as $postId => $position) {
                $where = ['category_id = ?' => (int) $id, 'item_id = ?' => (int) $postId];
                $bind  = ['position' => (int) $position];
                $adapter->update($this->categoryPostTable, $bind, $where);
            }
        }
        if (!empty($insert) || !empty($delete)) {
            $postIds = array_unique(array_merge(array_keys($insert), array_keys($delete)));
            $this->eventManager->dispatch(
                'magelearn_item_category_change_posts',
                ['category' => $category, 'item_ids' => $postIds]
            );
        }
        if (!empty($insert) || !empty($update) || !empty($delete)) {
            $category->setIsChangedPostList(true);
            $postIds = array_keys($insert + $delete + $update);
            $category->setAffectedPostIds($postIds);
        }

        return $this;
    }
}

Add Model/ResourceModel/Tag.php file.

<?php

namespace Magelearn\ProductsGrid\Model\ResourceModel;

use Magento\Framework\App\RequestInterface;
use Magento\Framework\Event\ManagerInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Model\AbstractModel;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
use Magento\Framework\Model\ResourceModel\Db\Context;
use Magento\Framework\Stdlib\DateTime\DateTime;
use Magelearn\ProductsGrid\Helper\Data;

/**
 * Class Tag
 * @package Magelearn\ProductsGrid\Model\ResourceModel
 */
class Tag extends AbstractDb
{
    /**
     * Date model
     *
     * @var DateTime
     */
    public $date;

    /**
     * Event Manager
     *
     * @var ManagerInterface
     */
    public $eventManager;

    /**
     * Post relation model
     *
     * @var string
     */
    public $tagPostTable;

    /**
     * @var Data
     */
    public $helperData;

    /**
     * @var RequestInterface
     */
    private $request;

    /**
     * Tag constructor.
     *
     * @param Context $context
     * @param ManagerInterface $eventManager
     * @param DateTime $date
     * @param RequestInterface $request
     * @param Data $helperData
     */
    public function __construct(
        Context $context,
        ManagerInterface $eventManager,
        DateTime $date,
        RequestInterface $request,
        Data $helperData
    ) {
        $this->helperData = $helperData;
        $this->date = $date;
        $this->eventManager = $eventManager;
        $this->request = $request;

        parent::__construct($context);

        $this->tagPostTable = $this->getTable('magelearn_item_post_tag');
    }

    /**
     * Initialize resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init('magelearn_item_tag', 'tag_id');
    }

    /**
     * Retrieves Tag Name from DB by passed id.
     *
     * @param $id
     *
     * @return string
     * @throws LocalizedException
     */
    public function getTagNameById($id)
    {
        $adapter = $this->getConnection();
        $select = $adapter->select()
            ->from($this->getMainTable(), 'name')
            ->where('tag_id = :tag_id');
        $binds = ['tag_id' => (int)$id];

        return $adapter->fetchOne($select, $binds);
    }

    /**
     * @inheritdoc
     */
    protected function _beforeSave(AbstractModel $object)
    {
        $object->setUpdatedAt($this->date->date());
        if ($object->isObjectNew()) {
            $object->setCreatedAt($this->date->date());
        }

        if (is_array($object->getStoreIds())) {
            $object->setStoreIds(implode(',', $object->getStoreIds()));
        }

        return parent::_beforeSave($object);
    }

    /**
     * @inheritdoc
     */
    protected function _afterSave(AbstractModel $object)
    {
        $this->savePostRelation($object);

        return parent::_afterSave($object);
    }

    /**
     * @param \Magelearn\ProductsGrid\Model\Tag $tag
     *
     * @return array
     */
    public function getPostsPosition(\Magelearn\ProductsGrid\Model\Tag $tag)
    {
        $select = $this->getConnection()->select()
            ->from($this->tagPostTable, ['item_id', 'position'])
            ->where('tag_id = :tag_id');

        $bind = ['tag_id' => (int)$tag->getId()];

        return $this->getConnection()->fetchPairs($select, $bind);
    }

    /**
     * @param \Magelearn\ProductsGrid\Model\Tag $tag
     *
     * @return $this
     */
    protected function savePostRelation(\Magelearn\ProductsGrid\Model\Tag $tag)
    {
        $tag->setIsChangedPostList(false);
        $id = $tag->getId();
        $posts = $tag->getPostsData();
        $oldPosts = $tag->getPostsPosition();
        if (is_array($posts)) {
            $insert = array_diff_key($posts, $oldPosts);
            $delete = array_diff_key($oldPosts, $posts);
            $update = array_intersect_key($posts, $oldPosts);
            $_update = [];
            foreach ($update as $key => $settings) {
                if (isset($oldPosts[$key]) && $oldPosts[$key] != $settings['position']) {
                    $_update[$key] = $settings;
                }
            }
            $update = $_update;
        }
        $adapter = $this->getConnection();
        if ($posts === null && $this->request->getActionName() === 'save') {
            foreach (array_keys($oldPosts) as $value) {
                $condition = ['item_id =?' => (int)$value, 'tag_id=?' => (int)$id];
                $adapter->delete($this->tagPostTable, $condition);
            }

            return $this;
        }
        if (!empty($delete)) {
            foreach (array_keys($delete) as $value) {
                $condition = ['item_id =?' => (int)$value, 'tag_id=?' => (int)$id];
                $adapter->delete($this->tagPostTable, $condition);
            }
        }
        if (!empty($insert)) {
            $data = [];
            foreach ($insert as $postId => $position) {
                $data[] = [
                    'tag_id' => (int)$id,
                    'item_id' => (int)$postId,
                    'position' => (int)$position['position']
                ];
            }
            $adapter->insertMultiple($this->tagPostTable, $data);
        }
        if (!empty($update)) {
            foreach ($update as $postId => $position) {
                $where = ['tag_id = ?' => (int)$id, 'item_id = ?' => (int)$postId];
                $bind = ['position' => (int)$position['position']];
                $adapter->update($this->tagPostTable, $bind, $where);
            }
        }
        if (!empty($insert) || !empty($delete)) {
            $postIds = array_unique(array_merge(array_keys($insert), array_keys($delete)));
            $this->eventManager->dispatch(
                'magelearn_item_tag_change_posts',
                ['tag' => $tag, 'item_ids' => $postIds]
            );
        }
        if (!empty($insert) || !empty($update) || !empty($delete)) {
            $tag->setIsChangedPostList(true);
            $postIds = array_keys($insert + $delete + $update);
            $tag->setAffectedPostIds($postIds);
        }

        return $this;
    }
}

Now add Model/ResourceModel/Swatch.php file.

<?php
declare(strict_types=1);

namespace Magelearn\ProductsGrid\Model\ResourceModel;

use Magento\Framework\Model\ResourceModel\Db\AbstractDb;

class Swatch extends AbstractDb
{
    /**
     * Define resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init('magelearn_item_post_swatch', 'swatch_id');
    }
}

Now we will add our collections files at Model/ResourceModel/Post/Collection.php

<?php

namespace Magelearn\ProductsGrid\Model\ResourceModel\Post;

use Magento\Framework\DB\Select;
use Magento\Sales\Model\ResourceModel\Collection\AbstractCollection;
use Magelearn\ProductsGrid\Model\Post;
use Zend_Db_Select;

/**
 * Class Collection
 * @package Magelearn\ProductsGrid\Model\ResourceModel\Post
 */
class Collection extends AbstractCollection
{
    /**
     * ID Field Name
     *
     * @var string
     */
    protected $_idFieldName = 'item_id';

    /**
     * Event prefix
     *
     * @var string
     */
    protected $_eventPrefix = 'magelearn_item_post_collection';

    /**
     * Event object
     *
     * @var string
     */
    protected $_eventObject = 'post_collection';

    /**
     * Define resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init(Post::class, \Magelearn\ProductsGrid\Model\ResourceModel\Post::class);
    }

    /**
     * Get SQL for get record count.
     * Extra GROUP BY strip added.
     *
     * @return Select
     */
    public function getSelectCountSql()
    {
        $countSelect = parent::getSelectCountSql();
        $countSelect->reset(Zend_Db_Select::GROUP);

        return $countSelect;
    }

    /**
     * @param null $valueField
     * @param string $labelField
     * @param array $additional
     *
     * @return array
     */
    protected function _toOptionArray($valueField = null, $labelField = 'name', $additional = [])
    {
        $valueField = 'item_id';

        return parent::_toOptionArray($valueField, $labelField, $additional); // TODO: Change the autogenerated stub
    }
}

Add file at Model/ResourceModel/Category/Collection.php

<?php

namespace Magelearn\ProductsGrid\Model\ResourceModel\Category;

use Magento\Eav\Model\Entity\Attribute;
use Magento\Framework\Data\Collection\AbstractDb;
use Magento\Framework\DB\Select;
use Magento\Sales\Model\ResourceModel\Collection\AbstractCollection;
use Magelearn\ProductsGrid\Model\Category;
use Magelearn\ProductsGrid\Model\ResourceModel\Category as CategoryResourceModel;
use Zend_Db_Select;

/**
 * Class Collection
 * @package Magelearn\ProductsGrid\Model\ResourceModel\Category
 */
class Collection extends AbstractCollection
{
    /**
     * ID Field Name
     *
     * @var string
     */
    protected $_idFieldName = 'category_id';

    /**
     * Event prefix
     *
     * @var string
     */
    protected $_eventPrefix = 'magelearn_item_category_collection';

    /**
     * Event object
     *
     * @var string
     */
    protected $_eventObject = 'category_collection';

    /**
     * Define resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init(Category::class, CategoryResourceModel::class);
    }

    /**
     * @param Attribute|string $field
     * @param null $condition
     *
     * @return AbstractDb|AbstractCollection
     */
    public function addAttributeToFilter($field, $condition = null)
    {
        return $this->addFieldToFilter($field, $condition);
    }

    /**
     * @inheritdoc
     */
    public function addFieldToFilter($field, $condition = null)
    {
        if ($field === 'entity_id') {
            $field = 'category_id';
        }

        return parent::addFieldToFilter($field, $condition);
    }

    /**
     * @return $this
     */
    public function setProductStoreId()
    {
        return $this;
    }

    /**
     * @return $this
     */
    public function setStoreId()
    {
        return $this;
    }

    /**
     * @param string $attribute
     * @param bool $joinType
     *
     * @return $this
     */
    public function addAttributeToSelect($attribute, $joinType = false)
    {
        return $this;
    }

    /**
     * Get SQL for get record count.
     * Extra GROUP BY strip added.
     *
     * @return Select
     */
    public function getSelectCountSql()
    {
        $countSelect = parent::getSelectCountSql();
        $countSelect->reset(Zend_Db_Select::GROUP);

        return $countSelect;
    }

    /**
     * @param null $valueField
     * @param string $labelField
     * @param array $additional
     *
     * @return array
     */
    protected function _toOptionArray($valueField = null, $labelField = 'name', $additional = [])
    {
        $valueField = 'category_id';

        return parent::_toOptionArray($valueField, $labelField, $additional); // TODO: Change the autogenerated stub
    }

    /**
     * Add if filter
     *
     * @param array|mixed $categoryIds
     *
     * @return $this
     */
    public function addIdFilter($categoryIds)
    {
        $condition = '';

        if (is_array($categoryIds)) {
            if (!empty($categoryIds)) {
                $condition = ['in' => $categoryIds];
            }
        } elseif (is_numeric($categoryIds)) {
            $condition = $categoryIds;
        } elseif (is_string($categoryIds)) {
            $ids = explode(',', $categoryIds);
            if (empty($ids)) {
                $condition = $categoryIds;
            } else {
                $condition = ['in' => $ids];
            }
        }

        if ($condition) {
            $this->addFieldToFilter('category_id', $condition);
        }

        return $this;
    }
}

Add file at Model/ResourceModel/Tag/Collection.php

<?php

namespace Magelearn\ProductsGrid\Model\ResourceModel\Tag;

use Magento\Framework\DB\Select;
use Magento\Sales\Model\ResourceModel\Collection\AbstractCollection;
use Magelearn\ProductsGrid\Model\Tag;
use Magelearn\ProductsGrid\Model\ResourceModel\Tag as TagResourceModel;
use Zend_Db_Select;

/**
 * Class Collection
 * @package Magelearn\ProductsGrid\Model\ResourceModel\Tag
 */
class Collection extends AbstractCollection
{
    /**
     * ID Field Name
     *
     * @var string
     */
    protected $_idFieldName = 'tag_id';

    /**
     * Event prefix
     *
     * @var string
     */
    protected $_eventPrefix = 'magelearn_item_tag_collection';

    /**
     * Event object
     *
     * @var string
     */
    protected $_eventObject = 'tag_collection';

    /**
     * Define resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init(Tag::class, TagResourceModel::class);
    }

    /**
     * Get SQL for get record count.
     * Extra GROUP BY strip added.
     *
     * @return Select
     */
    public function getSelectCountSql()
    {
        $countSelect = parent::getSelectCountSql();
        $countSelect->reset(Zend_Db_Select::GROUP);

        return $countSelect;
    }

    /**
     * @param null $valueField
     * @param string $labelField
     * @param array $additional
     *
     * @return array
     */
    protected function _toOptionArray($valueField = null, $labelField = 'name', $additional = [])
    {
        $valueField = 'tag_id';

        return parent::_toOptionArray($valueField, $labelField, $additional); // TODO: Change the autogenerated stub
    }

    /**
     * add if filter
     *
     * @param array|int|string $tagIds
     *
     * @return $this
     */
    public function addIdFilter($tagIds)
    {
        $condition = '';

        if (is_array($tagIds)) {
            if (!empty($tagIds)) {
                $condition = ['in' => $tagIds];
            }
        } elseif (is_numeric($tagIds)) {
            $condition = $tagIds;
        } elseif (is_string($tagIds)) {
            $ids = explode(',', $tagIds);
            if (empty($ids)) {
                $condition = $tagIds;
            } else {
                $condition = ['in' => $ids];
            }
        }

        if ($condition) {
            $this->addFieldToFilter('tag_id', $condition);
        }

        return $this;
    }
}

Add file at Model/ResourceModel/Swatch/Collection.php

<?php
namespace Magelearn\ProductsGrid\Model\ResourceModel\Swatch;

use Magento\Framework\DB\Select;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
use Magelearn\ProductsGrid\Model\Swatch;
use Magelearn\ProductsGrid\Model\ResourceModel\Swatch as SwatchResourceModel;
use Zend_Db_Select;

class Collection extends AbstractCollection
{
    /**
     * ID Field Name
     *
     * @var string
     */
    protected $_idFieldName = 'swatch_id';
    
    /**
     * Event prefix
     *
     * @var string
     */
    protected $_eventPrefix = 'magelearn_item_swatch_collection';
    
    /**
     * Event object
     *
     * @var string
     */
    protected $_eventObject = 'swatch_collection';
    
    /**
     * Define resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init(Swatch::class, SwatchResourceModel::class);
    }
}

Now we will first create a controller and layout file for Tag and Category.

Category Management

Add  Controller/Adminhtml/Category/Index.php file.

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Category;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\View\Result\Page;
use Magento\Framework\View\Result\PageFactory;

/**
 * Class Index
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Category
 */
class Index extends Action
{
    /**
     * Page result factory
     *
     * @var PageFactory
     */
    public $resultPageFactory;
    
    /**
     * Page factory
     *
     * @var \Magento\Backend\Model\View\Result\Page
     */
    public $resultPage;

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

    /**
     * execute action
     *
     * @return Forward
     */
    public function execute()
    {
        $resultPage = $this->resultPageFactory->create();
        $resultPage->getConfig()->getTitle()->prepend(__('Categories'));

        return $resultPage;
    }
}

Add view/adminhtml/layout/magelearn_productsgrid_category_index.xml file.

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="styles"/>
    <body>
        <referenceBlock name="menu">
            <action method="setActive">
                <argument name="itemId" xsi:type="string">Magelearn_ProductsGrid::category</argument>
            </action>
        </referenceBlock>
        <referenceBlock name="page.title">
            <action method="setTitleClass">
                <argument name="class" xsi:type="string">complex</argument>
            </action>
        </referenceBlock>
        <referenceContainer name="content">
            <uiComponent name="magelearn_item_category_listing"/>
        </referenceContainer>
    </body>
</page>

As per the Highlighted code above add view/adminhtml/ui_component/magelearn_item_category_listing.xml file.

<?xml version="1.0"?>
<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">magelearn_item_category_listing.magelearn_item_category_listing_data_source</item>
            <item name="deps" xsi:type="string">magelearn_item_category_listing.magelearn_item_category_listing_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">magelearn_item_category_columns</item>
        <item name="buttons" xsi:type="array">
            <item name="add" xsi:type="array">
                <item name="name" xsi:type="string">add</item>
                <item name="label" xsi:type="string" translate="true">Add New Category</item>
                <item name="class" xsi:type="string">primary</item>
                <item name="url" xsi:type="string">*/*/new</item>
            </item>
        </item>
    </argument>
    <dataSource name="magelearn_item_category_listing_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider</argument>
            <argument name="name" xsi:type="string">magelearn_item_category_listing_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">category_id</argument>
            <argument name="requestFieldName" xsi:type="string">category_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="indexField" xsi:type="string">category_id</item>
                    </item>
                </item>
            </argument>
        </argument>
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
            </item>
        </argument>
    </dataSource>
    <listingToolbar name="listing_top">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="sticky" xsi:type="boolean">true</item>
            </item>
        </argument>
        <bookmark name="bookmarks"/>
        <columnsControls name="columns_controls"/>
        <exportButton name="export_button"/>
        <filters name="listing_filters"/>
        <massaction name="listing_massaction">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/tree-massactions</item>
                </item>
            </argument>
            <action name="delete">
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="type" xsi:type="string">delete</item>
                        <item name="label" xsi:type="string" translate="true">Delete</item>
                        <item name="url" xsi:type="url" path="magelearn_productsgrid/category/massDelete"/>
                        <item name="confirm" xsi:type="array">
                            <item name="title" xsi:type="string" translate="true">Delete Categories</item>
                            <item name="message" xsi:type="string" translate="true">Are you sure you wan't to delete selected Categories?</item>
                        </item>
                    </item>
                </argument>
            </action>
            <action name="status">
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="type" xsi:type="string">status</item>
                        <item name="label" xsi:type="string" translate="true">Change status</item>
                    </item>
                </argument>
                <argument name="actions" xsi:type="array">
                    <item name="0" xsi:type="array">
                        <item name="type" xsi:type="string">enable</item>
                        <item name="label" xsi:type="string" translate="true">Enable</item>
                        <item name="url" xsi:type="url" path="magelearn_productsgrid/category/massStatus">
                            <param name="status">1</param>
                        </item>
                    </item>
                    <item name="1" xsi:type="array">
                        <item name="type" xsi:type="string">disable</item>
                        <item name="label" xsi:type="string" translate="true">Disable</item>
                        <item name="url" xsi:type="url" path="magelearn_productsgrid/category/massStatus">
                            <param name="status">0</param>
                        </item>
                    </item>
                </argument>
            </action>
        </massaction>
        <paging name="listing_paging"/>
    </listingToolbar>
    <columns name="magelearn_item_category_columns">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="editorConfig" xsi:type="array">
                    <item name="selectProvider" xsi:type="string">magelearn_item_category_listing.magelearn_item_category_listing.magelearn_item_category_columns.ids</item>
                    <item name="enabled" xsi:type="boolean">true</item>
                    <item name="indexField" xsi:type="string">category_id</item>
                    <item name="clientConfig" xsi:type="array">
                        <item name="saveUrl" xsi:type="url" path="magelearn_productsgrid/category/inlineEdit"/>
                        <item name="validateBeforeSave" xsi:type="boolean">false</item>
                    </item>
                </item>
                <item name="childDefaults" xsi:type="array">
                    <item name="fieldAction" xsi:type="array">
                        <item name="provider" xsi:type="string">magelearn_item_category_listing.magelearn_item_category_listing.magelearn_item_category_columns_editor</item>
                        <item name="target" xsi:type="string">startEdit</item>
                        <item name="params" xsi:type="array">
                            <item name="0" xsi:type="string">${ $.$data.rowIndex }</item>
                            <item name="1" xsi:type="boolean">true</item>
                        </item>
                    </item>
                </item>
            </item>
        </argument>
        <selectionsColumn name="ids">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="resizeEnabled" xsi:type="boolean">false</item>
                    <item name="resizeDefaultWidth" xsi:type="string">55</item>
                    <item name="indexField" xsi:type="string">category_id</item>
                </item>
            </argument>
        </selectionsColumn>
        <column name="category_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>
            </argument>
        </column>
        <column name="name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">text</item>
                    <item name="editor" xsi:type="array">
                        <item name="editorType" xsi:type="string">text</item>
                        <item name="validation" xsi:type="array">
                            <item name="required-entry" xsi:type="boolean">true</item>
                        </item>
                    </item>
                    <item name="label" xsi:type="string" translate="true">Name</item>
                </item>
            </argument>
        </column>
        <column name="enabled">
            <argument name="data" xsi:type="array">
                <item name="options" xsi:type="object">Magento\Config\Model\Config\Source\Enabledisable</item>
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">select</item>
                    <item name="editor" xsi:type="string">select</item>
                    <item name="label" xsi:type="string" translate="true">Status</item>
                    <item name="component" xsi:type="string">Magelearn_ProductsGrid/js/grid/columns/enable</item>
                    <item name="dataType" xsi:type="string">select</item>
                </item>
            </argument>
        </column>
        <column name="created_at" 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</item>
                </item>
            </argument>
        </column>
        <column name="updated_at" 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">Modified</item>
                    <item name="visible" xsi:type="boolean">false</item>
                </item>
            </argument>
        </column>
        <actionsColumn name="actions" class="Magelearn\ProductsGrid\Ui\Component\Listing\Column\CategoryActions">
            <settings>
                <indexField>category_id</indexField>
                <resizeEnabled>false</resizeEnabled>
                <resizeDefaultWidth>107</resizeDefaultWidth>
            </settings>
        </actionsColumn>
    </columns>
</listing>

Now as per the highlighted code above, we will add our mass action files.

Add file at Controller/Adminhtml/Category/MassDelete.php 

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Category;

use Exception;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Backend\Model\View\Result\Redirect;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Ui\Component\MassAction\Filter;
use Magelearn\ProductsGrid\Model\ResourceModel\Category\CollectionFactory;

/**
 * Class MassDelete
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Category
 */
class MassDelete extends Action
{
    /**
     * @var Filter
     */
    public $filter;

    /**
     * @var CollectionFactory
     */
    public $collectionFactory;

    /**
     * MassDelete constructor.
     *
     * @param Context $context
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(
        Context $context,
        Filter $filter,
        CollectionFactory $collectionFactory
    ) {
        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;

        parent::__construct($context);
    }

    /**
     * @return $this|ResponseInterface|ResultInterface
     * @throws LocalizedException
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());

        try {
            $collection->walk('delete');
            $this->messageManager->addSuccessMessage(__('Categories has been deleted.'));
        } catch (Exception $e) {
            $this->messageManager->addSuccessMessage(__('Something wrong when delete Category.'));
        }

        /** @var Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);

        return $resultRedirect->setPath('*/*/');
    }
}

Add file at Controller/Adminhtml/Category/MassStatus.php

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Category;

use Exception;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Backend\Model\View\Result\Redirect;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Ui\Component\MassAction\Filter;
use Magelearn\ProductsGrid\Model\ResourceModel\Category\CollectionFactory;

/**
 * Class MassStatus
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Category
 */
class MassStatus extends Action
{
    /**
     * Mass Action Filter
     *
     * @var Filter
     */
    public $filter;

    /**
     * Collection Factory
     *
     * @var \Magelearn\ProductsGrid\Model\ResourceModel\Category\CollectionFactory
     */
    public $collectionFactory;

    /**
     * MassStatus constructor.
     *
     * @param Context $context
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(
        Context $context,
        Filter $filter,
        CollectionFactory $collectionFactory
    ) {
        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;

        parent::__construct($context);
    }

    /**
     * @return $this|ResponseInterface|ResultInterface
     * @throws LocalizedException
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());
        $status = (int)$this->getRequest()->getParam('status');

        $categoryUpdated = 0;
        foreach ($collection as $category) {
            try {
                $category->setEnabled($status)
                    ->save();

                    $categoryUpdated++;
            } catch (LocalizedException $e) {
                $this->messageManager->addErrorMessage($e->getMessage());
            } catch (Exception $e) {
                $this->_getSession()->addException(
                    $e,
                    __('Something went wrong while updating status for %1.', $category->getName())
                );
            }
        }

        if ($categoryUpdated) {
            $this->messageManager->addSuccessMessage(__('A total of %1 record(s) have been updated.', $categoryUpdated));
        }

        /** @var Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);

        return $resultRedirect->setPath('*/*/');
    }
}

Add file at Controller/Adminhtml/Category/InlineEdit.php

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Category;

use Exception;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\Result\Json;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\LocalizedException;
use Magelearn\ProductsGrid\Model\Category;
use Magelearn\ProductsGrid\Model\CategoryFactory;
use RuntimeException;

/**
 * Class InlineEdit
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Category
 */
class InlineEdit extends Action
{
    /**
     * @var JsonFactory
     */
    public $jsonFactory;

    /**
     * @var CategoryFactory
     */
    public $categoryFactory;

    /**
     * InlineEdit constructor.
     *
     * @param Context $context
     * @param JsonFactory $jsonFactory
     * @param CategoryFactory $categoryFactory
     */
    public function __construct(
        Context $context,
        JsonFactory $jsonFactory,
        CategoryFactory $categoryFactory
    ) {
        $this->jsonFactory = $jsonFactory;
        $this->categoryFactory = $categoryFactory;

        parent::__construct($context);
    }

    /**
     * @return ResultInterface
     */
    public function execute()
    {
        /** @var Json $resultJson */
        $resultJson = $this->jsonFactory->create();
        $error = false;
        $messages = [];
        $postItems = $this->getRequest()->getParam('items', []);
        if (!($this->getRequest()->getParam('isAjax') && !empty($postItems))) {
            return $resultJson->setData([
                'messages' => [__('Please correct the data sent.')],
                'error' => true,
            ]);
        }

        $key = array_keys($postItems);
        $categoryId = !empty($key) ? (int)$key[0] : '';
        /** @var Category $category */
        $category = $this->categoryFactory->create()->load($categoryId);
        try {
            $category->addData($postItems[$categoryId])
                ->save();
        } catch (LocalizedException $e) {
            $messages[] = $this->getErrorWithCategoryId($category, $e->getMessage());
            $error = true;
        } catch (RuntimeException $e) {
            $messages[] = $this->getErrorWithCategoryId($category, $e->getMessage());
            $error = true;
        } catch (Exception $e) {
            $messages[] = $this->getErrorWithCategoryId($category, __('Something went wrong while saving the Category.'));
            $error = true;
        }

        return $resultJson->setData([
            'messages' => $messages,
            'error' => $error
        ]);
    }

    /**
     * Add Category id to error message
     *
     * @param Category $category
     * @param string $errorText
     *
     * @return string
     */
    public function getErrorWithCategoryId(Category $category, $errorText)
    {
        return '[Category ID: ' . $category->getId() . '] ' . $errorText;
    }
}

To add Enable/Disable buttons properly, add JS file at
view/adminhtml/web/js/grid/columns/enable.js

define([
    'Magento_Ui/js/grid/columns/select'
], function (Column) {
    'use strict';

    return Column.extend({
        defaults: {
            bodyTmpl: 'ui/grid/cells/html'
        },
        getLabel: function (record) {
            var label = this._super(record);

            if (label !== '') {
                switch (record.enabled) {
                    case '1':
                        label = '<span class="grid-severity-notice"><span>' + label + '</span></span>';
                        break;
                    case '0':
                        label = '<span class="grid-severity-critical"><span>' + label + '</span></span>';
                        break;
                    case '2':
                        label = '<span class="grid-severity-critical"><span>' + label + '</span></span>';
                        break;
                }
            }
            return label;
        }
    });
});

To define Category actions, add a file at Ui/Component/Listing/Column/CategoryActions.php

<?php

namespace Magelearn\ProductsGrid\Ui\Component\Listing\Column;

use Magento\Framework\UrlInterface;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Ui\Component\Listing\Columns\Column;

/**
 * Class Actions
 * @package Magelearn\ProductsGrid\Ui\Component\Listing\Columns
 */
class CategoryActions extends Column
{
    const URL_PATH_EDIT = 'magelearn_productsgrid/category/edit';
    const URL_PATH_DELETE = 'magelearn_productsgrid/category/delete';

    /**
     * @var UrlInterface
     */
    protected $urlBuilder;

    /**
     * Constructor
     *
     * @param ContextInterface $context
     * @param UiComponentFactory $uiComponentFactory
     * @param UrlInterface $urlBuilder
     * @param array $components
     * @param array $data
     */
    public function __construct(
        ContextInterface $context,
        UiComponentFactory $uiComponentFactory,
        UrlInterface $urlBuilder,
        array $components = [],
        array $data = []
    ) {
        $this->urlBuilder = $urlBuilder;

        parent::__construct($context, $uiComponentFactory, $components, $data);
    }

    /**
     * Prepare Data Source
     *
     * @param array $dataSource
     *
     * @return array
     */
    public function prepareDataSource(array $dataSource)
    {
        if (isset($dataSource['data']['items'])) {
            foreach ($dataSource['data']['items'] as &$item) {
                $item[$this->getData('name')] = [
                    'edit' => [
                        'href' => $this->urlBuilder->getUrl(
                            static::URL_PATH_EDIT,
                            [
                                'id' => $item['category_id']
                            ]
                            ),
                        'label' => __('Edit')
                    ],
                    'delete' => [
                        'href' => $this->urlBuilder->getUrl(
                            static::URL_PATH_DELETE,
                            [
                                'id' => $item['category_id']
                            ]
                            ),
                        'label' => __('Delete'),
                        'confirm' => [
                            'title' => __('Delete %1',$item['category_id']),
                            'message' => __('Are you sure you wan\'t to delete a %1 record ?',$item['category_id'])
                        ]
                    ]
                ];
            }
        }

        return $dataSource;
    }
}

As per the highlighted code above, we will add our Delete action file at Controller/Adminhtml/Category/Delete.php 

<?php
declare(strict_types=1);
 
namespace Magelearn\ProductsGrid\Controller\Adminhtml\Category;

use Magento\Backend\App\Action\Context;
use Magento\Framework\Registry;
use Magento\Framework\View\Result\Page;
use Magento\Framework\View\Result\PageFactory;
use Magelearn\ProductsGrid\Controller\Adminhtml\Category;
use Magelearn\ProductsGrid\Model\CategoryFactory;
 
class Delete extends Category
{
    /**
     * Category Factory
     *
     * @var CategoryFactory
     */
    public $categoryFactory;
    
    /**
     * Page factory
     *
     * @var PageFactory
     */
    public $resultPageFactory;
    
    /**
     * Delete constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param CategoryFactory $categoryFactory
     * @param PageFactory $resultPageFactory
     */
    public function __construct(
        Context $context,
        Registry $coreRegistry,
        CategoryFactory $categoryFactory,
        PageFactory $resultPageFactory
        ) {
            $this->resultPageFactory = $resultPageFactory;
            
            parent::__construct($context, $coreRegistry, $categoryFactory);
    }
 
    /**
     * Delete action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultRedirectFactory->create();
        // check if we know what should be deleted
        $id = $this->getRequest()->getParam('id');
        if ($id) {
            try {
                // init model and delete
                $model = $this->initCategory();
                $model->delete();
                // display success message
                $this->messageManager->addSuccessMessage(__('You deleted the Category.'));
                // go to grid
                return $resultRedirect->setPath('*/*/');
            } catch (\Exception $e) {
                // display error message
                $this->messageManager->addErrorMessage($e->getMessage());
                // go back to edit form
                return $resultRedirect->setPath('*/*/edit', ['id' => $id]);
            }
        }
        // display error message
        $this->messageManager->addErrorMessage(__('We can\'t find a Category to delete.'));
        // go to grid
        return $resultRedirect->setPath('*/*/');
    }
}

As per the highlighted code above, Add file at Controller/Adminhtml/Category.php

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Registry;
use Magelearn\ProductsGrid\Model\CategoryFactory;

/**
 * Class Category
 * @package Magelearn\ProductsGrid\Controller\Adminhtml
 */
abstract class Category extends Action
{
    /** Authorization level of a basic admin session */
    const ADMIN_RESOURCE = 'Magelearn_ProductsGrid::category';

    /**
     * Post Category Factory
     *
     * @var CategoryFactory
     */
    public $categoryFactory;

    /**
     * Core registry
     *
     * @var Registry
     */
    public $coreRegistry;

    /**
     * Category constructor.
     *
     * @param Context $context
     * @param Registry $coreRegistry
     * @param CategoryFactory $categoryFactory
     */
    public function __construct(
        Context $context,
        Registry $coreRegistry,
        CategoryFactory $categoryFactory
    ) {
        $this->categoryFactory = $categoryFactory;
        $this->coreRegistry = $coreRegistry;

        parent::__construct($context);
    }

    /**
     * @param bool $register
     * @param bool $isSave
     *
     * @return bool|\Magelearn\ProductsGrid\Model\Category
     */
    public function initCategory($register = false)
    {
        $categoryId = (int)$this->getRequest()->getParam('id');

        /** @var \Magelearn\ProductsGrid\Model\Category $category */
        //Model/Category
        $category = $this->categoryFactory->create();
        if ($categoryId) {
            $category->load($categoryId);
            if (!$category->getId()) {
                $this->messageManager->addErrorMessage(__('This category no longer exists.'));
                
                return false;
            }
        }

        if ($register) {
            $this->coreRegistry->register('magelearn_item_category', $category);
        }

        return $category;
    }
}

Now we will add New Action for Category. Add file at Controller/Adminhtml/Category/NewAction.php

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Category;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Backend\Model\View\Result\Forward;
use Magento\Backend\Model\View\Result\ForwardFactory;

/**
 * Class NewAction
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Category
 */
class NewAction extends Action
{
    /**
     * @var ForwardFactory
     */
    public $resultForwardFactory;

    /**
     * NewAction constructor.
     *
     * @param ForwardFactory $resultForwardFactory
     * @param Context $context
     */
    public function __construct(
        ForwardFactory $resultForwardFactory,
        Context $context
    ) {
        $this->resultForwardFactory = $resultForwardFactory;

        parent::__construct($context);
    }

    /**
     * forward to edit
     *
     * @return Forward
     */
    public function execute()
    {
        $resultForward = $this->resultForwardFactory->create();
        $resultForward->forward('edit');

        return $resultForward;
    }
}

For Edit Action add a file at Controller/Adminhtml/Category/Edit.php

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Category;

use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\Result\Redirect;
use Magento\Framework\Registry;
use Magento\Framework\View\Result\Page;
use Magento\Framework\View\Result\PageFactory;
use Magelearn\ProductsGrid\Controller\Adminhtml\Category;
use Magelearn\ProductsGrid\Model\CategoryFactory;

/**
 * Class Edit
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Category
 */
class Edit extends Category
{
    /**
     * Page factory
     *
     * @var PageFactory
     */
    public $resultPageFactory;

    /**
     * Edit constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param CategoryFactory $categoryFactory
     * @param PageFactory $resultPageFactory
     */
    public function __construct(
        Context $context,
        Registry $registry,
        CategoryFactory $categoryFactory,
        PageFactory $resultPageFactory
    ) {
        $this->resultPageFactory = $resultPageFactory;

        parent::__construct($context, $registry, $categoryFactory);
    }

    /**
     * @return \Magento\Backend\Model\View\Result\Page|Redirect|Page
     */
    public function execute()
    {
        $category = $this->initCategory();
        if (!$category) {
            $resultRedirect = $this->resultRedirectFactory->create();
            $resultRedirect->setPath('*');

            return $resultRedirect;
        }

        /**
         * Check if we have data in session
         */
        $data = $this->_session->getData('magelearn_item_category_data', true);
        if (!empty($data)) {
            $category->setData($data);
        }
            
        $this->coreRegistry->register('magelearn_item_category', $category);

        /** @var Page $resultPage */
        $resultPage = $this->resultPageFactory->create();
        $resultPage->setActiveMenu('Magelearn_ProductsGrid::category');
        $resultPage->getConfig()->getTitle()->set(__('Categories'));

        $title = $category->getId() ? $category->getName() : __('New Category');
        $resultPage->getConfig()->getTitle()->prepend($title);

        return $resultPage;
    }
}

Now add view/adminhtml/layout/magelearn_productsgrid_category_edit.xml file.

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="editor"/>
    <body>
        <referenceContainer name="content">
            <block class="Magelearn\ProductsGrid\Block\Adminhtml\Category\Edit" name="magelearn.item.category.edit"/>
        </referenceContainer>
        <referenceContainer name="left">
            <block class="Magelearn\ProductsGrid\Block\Adminhtml\Category\Edit\Tabs" name="magelearn_item_category_tabs">
                <block class="Magelearn\ProductsGrid\Block\Adminhtml\Category\Edit\Tab\Category" name="magelearn_item_category_edit_tab_category"/>
                <block class="Magelearn\ProductsGrid\Block\Adminhtml\Category\Edit\Tab\Post" name="magelearn_item_category_edit_tab_post"/>
                <action method="addTab">
                    <argument name="name" xsi:type="string">category</argument>
                    <argument name="block" xsi:type="string">magelearn_item_category_edit_tab_category</argument>
                </action>
                <action method="addTab">
                    <argument name="name" xsi:type="string">post</argument>
                    <argument name="block" xsi:type="string">magelearn_item_category_edit_tab_post</argument>
                </action>
            </block>
        </referenceContainer>
    </body>
</page>

Now as per the highlighted code above add the file at Block/Adminhtml/Category/Edit.php

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Category;

use Magento\Backend\Block\Widget\Context;
use Magento\Backend\Block\Widget\Form\Container;
use Magento\Framework\Registry;
use Magelearn\ProductsGrid\Model\Category;

/**
 * Class Edit
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Category
 */
class Edit extends Container
{
    /**
     * Core registry
     *
     * @var Registry
     */
    public $coreRegistry;

    /**
     * Edit constructor.
     *
     * @param Registry $coreRegistry
     * @param Context $context
     * @param array $data
     */
    public function __construct(
        Registry $coreRegistry,
        Context $context,
        array $data = []
    ) {
        $this->coreRegistry = $coreRegistry;

        parent::__construct($context, $data);
    }

    /**
     * prepare the form
     */
    protected function _construct()
    {
        $this->_blockGroup = 'Magelearn_ProductsGrid';
        $this->_controller = 'adminhtml_category';

        parent::_construct();
        
        $this->buttonList->add(
            'save-and-continue',
            [
                'label' => __('Save and Continue Edit'),
                'class' => 'save',
                'data_attribute' => [
                    'mage-init' => [
                        'button' => [
                            'event' => 'saveAndContinueEdit',
                            'target' => '#edit_form'
                        ]
                    ]
                ]
            ],
            -100
        );
    }
    
    /**
     * Retrieve text for header element depending on loaded Category
     *
     * @return string
     */
    public function getHeaderText()
    {
        /** @var Tag $tag */
        $category = $this->coreRegistry->registry('magelearn_item_category');
        if ($category->getId()) {
            return __("Edit Category '%1'", $this->escapeHtml($category->getName()));
        }
        
        return __('New Category');
    }
}

Add Block/Adminhtml/Category/Edit/Tabs.php file.

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Category\Edit;

/**
 * Class Tabs
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Category\Edit
 */
class Tabs extends \Magento\Backend\Block\Widget\Tabs
{
    /**
     * constructor
     *
     * @return void
     */
    protected function _construct()
    {
        parent::_construct();
        $this->setId('category_tabs');
        $this->setDestElementId('edit_form');
        $this->setTitle(__('Category Information'));
    }
}

Add Block/Adminhtml/Category/Edit/Form.php file.

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Category\Edit;

use Magento\Backend\Block\Widget\Form\Generic;

/**
 * Class Form
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Category\Edit
 */
class Form extends Generic
{
    /**
     * @inheritdoc
     */
    protected function _prepareForm()
    {
        /** @var \Magento\Framework\Data\Form $form */
        $form = $this->_formFactory->create([
            'data' => [
                'id' => 'edit_form',
                'action' => $this->getData('action'),
                'method' => 'post',
                'enctype' => 'multipart/form-data'
            ]
        ]);

        $form->setUseContainer(true);
        $this->setForm($form);

        return parent::_prepareForm();
    }
}

Add Block/Adminhtml/Category/Edit/Tab/Category.php file.

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Category\Edit\Tab;

use Magento\Backend\Block\Store\Switcher\Form\Renderer\Fieldset\Element;
use Magento\Backend\Block\Template\Context;
use Magento\Backend\Block\Widget\Form\Generic;
use Magento\Backend\Block\Widget\Tab\TabInterface;
use Magento\Cms\Model\Wysiwyg\Config;
use Magento\Config\Model\Config\Source\Enabledisable;
use Magento\Config\Model\Config\Source\Yesno;
use Magento\Framework\Data\Form\Element\Renderer\RendererInterface;
use Magento\Framework\Data\FormFactory;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Registry;

/**
 * Class Category
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Category\Edit\Tab
 */
class Category extends Generic implements TabInterface
{
    /**
     * Wysiwyg config
     *
     * @var Config
     */
    protected $wysiwygConfig;

    /**
     * Country options
     *
     * @var Yesno
     */
    protected $booleanOptions;

    /**
     * @var Enabledisable
     */
    protected $enableDisable;

    /**
     * Category constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param FormFactory $formFactory
     * @param Config $wysiwygConfig
     * @param Yesno $booleanOptions
     * @param Enabledisable $enableDisable
     * @param array $data
     */
    public function __construct(
        Context $context,
        Registry $registry,
        FormFactory $formFactory,
        Config $wysiwygConfig,
        Yesno $booleanOptions,
        Enabledisable $enableDisable,
        array $data = []
    ) {
        $this->wysiwygConfig = $wysiwygConfig;
        $this->booleanOptions = $booleanOptions;
        $this->enableDisable = $enableDisable;

        parent::__construct($context, $registry, $formFactory, $data);
    }

    /**
     * @return Generic
     * @throws LocalizedException
     * @throws NoSuchEntityException
     */
    protected function _prepareForm()
    {
        /** @var \Magelearn\ProductsGrid\Model\Category $category */
        $category = $this->_coreRegistry->registry('magelearn_item_category');

        $form = $this->_formFactory->create();
        $form->setHtmlIdPrefix('category_');
        $form->setFieldNameSuffix('category');

        $fieldset = $form->addFieldset('base_fieldset', [
            'legend' => __('Category Information'),
            'class' => 'fieldset-wide'
        ]);

        if ($category->getId()) {
            $fieldset->addField('category_id', 'hidden', ['name' => 'category_id']);
        }

        $fieldset->addField('name', 'text', [
            'name' => 'name',
            'label' => __('Name'),
            'title' => __('Name'),
            'required' => true,
        ]);
        
        $fieldset->addField('enabled', 'select', [
            'name' => 'enabled',
            'label' => __('Status'),
            'title' => __('Status'),
            'values' => $this->enableDisable->toOptionArray(),
        ]);
        if (!$category->hasData('enabled')) {
            $category->setEnabled(1);
        }

        $form->addValues($category->getData());
        $this->setForm($form);

        return parent::_prepareForm();
    }

    /**
     * Prepare label for tab
     *
     * @return string
     */
    public function getTabLabel()
    {
        return __('Category');
    }

    /**
     * Prepare title for tab
     *
     * @return string
     */
    public function getTabTitle()
    {
        return $this->getTabLabel();
    }

    /**
     * Can show tab in tabs
     *
     * @return boolean
     */
    public function canShowTab()
    {
        return true;
    }

    /**
     * Tab is hidden
     *
     * @return boolean
     */
    public function isHidden()
    {
        return false;
    }
}

Add Block/Adminhtml/Category/Edit/Tab/Post.php file.

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Category\Edit\Tab;

use Exception;
use Magento\Backend\Block\Template\Context;
use Magento\Backend\Block\Widget\Grid\Column;
use Magento\Backend\Block\Widget\Grid\Extended;
use Magento\Backend\Block\Widget\Tab\TabInterface;
use Magento\Backend\Helper\Data;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Registry;
use Magelearn\ProductsGrid\Model\PostFactory;
use Magelearn\ProductsGrid\Model\ResourceModel\Post\Collection;
use Magelearn\ProductsGrid\Model\ResourceModel\Post\CollectionFactory;

/**
 * Class Post
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Category\Edit\Tab
 */
class Post extends Extended implements TabInterface
{
    /**
     * @var CollectionFactory
     */
    public $postCollectionFactory;

    /**
     * @var Registry
     */
    public $coreRegistry;

    /**
     * @var PostFactory
     */
    public $postFactory;

    /**
     * Post constructor.
     *
     * @param Context $context
     * @param Registry $coreRegistry
     * @param Data $backendHelper
     * @param PostFactory $postFactory
     * @param CollectionFactory $postCollectionFactory
     * @param array $data
     */
    public function __construct(
        Context $context,
        Registry $coreRegistry,
        Data $backendHelper,
        PostFactory $postFactory,
        CollectionFactory $postCollectionFactory,
        array $data = []
    ) {
        $this->postCollectionFactory = $postCollectionFactory;
        $this->coreRegistry          = $coreRegistry;
        $this->postFactory           = $postFactory;

        parent::__construct($context, $backendHelper, $data);
    }

    /**
     * Set grid params
     */
    public function _construct()
    {
        parent::_construct();
        $this->setId('post_grid');
        $this->setDefaultSort('position');
        $this->setDefaultDir('ASC');
        $this->setUseAjax(true);

        if ($this->getCategory()->getId()) {
            $this->setDefaultFilter(['in_posts' => 1]);
        }
    }

    /**
     * @inheritdoc
     */
    protected function _prepareCollection()
    {
        /** @var Collection $collection */
        $collection = $this->postCollectionFactory->create();
        $collection->getSelect()->joinLeft(
            ['related' => $collection->getTable('magelearn_item_post_category')],
            'related.item_id=main_table.item_id AND related.category_id=' . (int) $this->getRequest()->getParam(
                'id',
                0
            ),
            ['position']
        );

        $collection->addFilterToMap('item_id', 'main_table.item_id');

        $this->setCollection($collection);

        return parent::_prepareCollection();
    }

    /**
     * @return $this
     * @throws Exception
     */
    protected function _prepareColumns()
    {
        $this->addColumn('in_posts', [
            'header_css_class' => 'a-center',
            'type'             => 'checkbox',
            'name'             => 'in_post',
            'values'           => $this->_getSelectedPosts(),
            'align'            => 'center',
            'index'            => 'item_id'
        ]);
        $this->addColumn('item_id', [
            'header'           => __('ID'),
            'sortable'         => true,
            'index'            => 'item_id',
            'type'             => 'number',
            'header_css_class' => 'col-id',
            'column_css_class' => 'col-id'
        ]);
        $this->addColumn('title', [
            'header'           => __('Name'),
            'index'            => 'name',
            'header_css_class' => 'col-name',
            'column_css_class' => 'col-name'
        ]);
        $this->addColumn('position', [
            'header'         => __('Position'),
            'name'           => 'position',
            'width'          => 60,
            'type'           => 'number',
            'validate_class' => 'validate-number',
            'index'          => 'position',
            'editable'       => true,
        ]);

        return $this;
    }

    /**
     * Retrieve selected Posts
     * @return array
     */
    protected function _getSelectedPosts()
    {
        $posts = $this->getRequest()->getPost('category_posts', null);
        if (!is_array($posts)) {
            $posts = $this->getCategory()->getPostsPosition();

            return array_keys($posts);
        }

        return $posts;
    }

    /**
     * Retrieve selected Posts
     * @return array
     */
    public function getSelectedPosts()
    {
        $selected = $this->getCategory()->getPostsPosition();
        if (!is_array($selected)) {
            $selected = [];
        } else {
            foreach ($selected as $key => $value) {
                $selected[$key] = ['position' => $value];
            }
        }

        return $selected;
    }

    /**
     * @param \Magelearn\ProductsGrid\Model\Post|Object $item
     *
     * @return string
     */
    public function getRowUrl($item)
    {
        return '#';
    }

    /**
     * get grid url
     *
     * @return string
     */
    public function getGridUrl()
    {
        return $this->getUrl('*/*/postsGrid', ['id' => $this->getCategory()->getId()]);
    }

    /**
     * @return \Magelearn\ProductsGrid\Model\Category
     */
    public function getCategory()
    {
        return $this->coreRegistry->registry('magelearn_item_category');
    }

    /**
     * @param Column $column
     *
     * @return $this
     * @throws LocalizedException
     */
    protected function _addColumnFilterToCollection($column)
    {
        if ($column->getId() === 'in_posts') {
            $postIds = $this->_getSelectedPosts();
            if (empty($postIds)) {
                $postIds = 0;
            }
            if ($column->getFilter()->getValue()) {
                $this->getCollection()->addFieldToFilter('main_table.item_id', ['in' => $postIds]);
            } elseif ($postIds) {
                $this->getCollection()->addFieldToFilter('main_table.item_id', ['nin' => $postIds]);
            }
        } else {
            parent::_addColumnFilterToCollection($column);
        }

        return $this;
    }

    /**
     * @return string
     */
    public function getTabLabel()
    {
        return __('Posts');
    }

    /**
     * @return bool
     */
    public function isHidden()
    {
        return false;
    }

    /**
     * @return string
     */
    public function getTabTitle()
    {
        return $this->getTabLabel();
    }

    /**
     * @return bool
     */
    public function canShowTab()
    {
        return true;
    }

    /**
     * @return string
     */
    public function getTabUrl()
    {
        return $this->getUrl('magelearn_productsgrid/category/posts', ['_current' => true]);
    }

    /**
     * @return string
     */
    public function getTabClass()
    {
        return 'ajax only';
    }
}

As per the highlighted code above, we will add Controller/Adminhtml/Category/Posts.php file.

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Category;

use Magento\Backend\App\Action\Context;
use Magento\Framework\Registry;
use Magento\Framework\View\Result\Layout;
use Magento\Framework\View\Result\LayoutFactory;
use Magelearn\ProductsGrid\Controller\Adminhtml\Category;
use Magelearn\ProductsGrid\Model\CategoryFactory;

/**
 * Class Posts
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Category
 */
class Posts extends Category
{
    /**
     * Result layout factory
     *
     * @var LayoutFactory
     */
    public $resultLayoutFactory;

    /**
     * Posts constructor.
     *
     * @param Context $context
     * @param Registry $coreRegistry
     * @param CategoryFactory $categoryFactory
     * @param LayoutFactory $resultLayoutFactory
     */
    public function __construct(
        Context $context,
        Registry $coreRegistry,
        CategoryFactory $categoryFactory,
        LayoutFactory $resultLayoutFactory
    ) {
        $this->resultLayoutFactory = $resultLayoutFactory;

        parent::__construct($context, $coreRegistry, $categoryFactory);
    }

    /**
     * @return Layout
     */
    public function execute()
    {
        $this->initCategory(true);

        return $this->resultLayoutFactory->create();
    }
}

And Controller/Adminhtml/Category/PostsGrid.php file.

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Category;

/**
 * Class PostsGrid
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Category
 */
class PostsGrid extends Posts
{
}

Also, add view/adminhtml/layout/magelearn_productsgrid_category_postsgrid.xml file.

<?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\ProductsGrid\Block\Adminhtml\Category\Edit\Tab\Post" name="category.edit.tab.post"/>
    </container>
</layout>

Add view/adminhtml/layout/magelearn_productsgrid_category_posts.xml file.

<?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" output="1">
        <block class="Magelearn\ProductsGrid\Block\Adminhtml\Category\Edit\Tab\Post" name="category.edit.tab.post"/>
        <block class="Magento\Backend\Block\Widget\Grid\Serializer" name="post_grid_serializer">
            <arguments>
                <argument name="input_names" xsi:type="string">position</argument>
                <argument name="grid_block" xsi:type="string">category.edit.tab.post</argument>
                <argument name="callback" xsi:type="string">getSelectedPosts</argument>
                <argument name="input_element_name" xsi:type="string">posts</argument>
                <argument name="reload_param_name" xsi:type="string">category_posts</argument>
            </arguments>
        </block>
    </container>
</layout>

Tag Management

Add Controller/Adminhtml/Tag/Index.php file.
<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Tag;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\View\Result\Page;
use Magento\Framework\View\Result\PageFactory;

/**
 * Class Index
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Tag
 */
class Index extends Action
{
    /**
     * Page result factory
     *
     * @var PageFactory
     */
    public $resultPageFactory;

    /**
     * Page factory
     *
     * @var \Magento\Backend\Model\View\Result\Page
     */
    public $resultPage;

    /**
     * Index constructor.
     *
     * @param Context $context
     * @param PageFactory $resultPageFactory
     */
    public function __construct(
        Context $context,
        PageFactory $resultPageFactory
    ) {
        $this->resultPageFactory = $resultPageFactory;

        parent::__construct($context);
    }

    /**
     * execute the action
     *
     * @return \Magento\Backend\Model\View\Result\Page|Page
     */
    public function execute()
    {
        $resultPage = $this->resultPageFactory->create();
        $resultPage->getConfig()->getTitle()->prepend(__('Tags'));

        return $resultPage;
    }
}

Add Layout at view/adminhtml/layout/magelearn_productsgrid_tag_index.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="styles"/>
    <body>
        <referenceBlock name="menu">
            <action method="setActive">
                <argument name="itemId" xsi:type="string">Magelearn_ProductsGrid::tag</argument>
            </action>
        </referenceBlock>
        <referenceBlock name="page.title">
            <action method="setTitleClass">
                <argument name="class" xsi:type="string">complex</argument>
            </action>
        </referenceBlock>
        <referenceContainer name="content">
            <uiComponent name="magelearn_item_tag_listing"/>
        </referenceContainer>
    </body>
</page>

Now add ui_component file at view/adminhtml/ui_component/magelearn_item_tag_listing.xml

<?xml version="1.0"?>
<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">magelearn_item_tag_listing.magelearn_item_tag_listing_data_source</item>
            <item name="deps" xsi:type="string">magelearn_item_tag_listing.magelearn_item_tag_listing_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">magelearn_item_tag_columns</item>
        <item name="buttons" xsi:type="array">
            <item name="add" xsi:type="array">
                <item name="name" xsi:type="string">add</item>
                <item name="label" xsi:type="string" translate="true">Add New Tag</item>
                <item name="class" xsi:type="string">primary</item>
                <item name="url" xsi:type="string">*/*/new</item>
            </item>
        </item>
    </argument>
    <dataSource name="magelearn_item_tag_listing_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider</argument>
            <argument name="name" xsi:type="string">magelearn_item_tag_listing_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">tag_id</argument>
            <argument name="requestFieldName" xsi:type="string">tag_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="indexField" xsi:type="string">tag_id</item>
                    </item>
                </item>
            </argument>
        </argument>
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
            </item>
        </argument>
    </dataSource>
    <listingToolbar name="listing_top">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="sticky" xsi:type="boolean">true</item>
            </item>
        </argument>
        <bookmark name="bookmarks"/>
        <columnsControls name="columns_controls"/>
        <exportButton name="export_button"/>
        <filters name="listing_filters"/>
        <massaction name="listing_massaction">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/tree-massactions</item>
                </item>
            </argument>
            <action name="delete">
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="type" xsi:type="string">delete</item>
                        <item name="label" xsi:type="string" translate="true">Delete</item>
                        <item name="url" xsi:type="url" path="magelearn_productsgrid/tag/massDelete"/>
                        <item name="confirm" xsi:type="array">
                            <item name="title" xsi:type="string" translate="true">Delete Tags</item>
                            <item name="message" xsi:type="string" translate="true">Are you sure you wan't to delete selected Tags?</item>
                        </item>
                    </item>
                </argument>
            </action>
            <action name="status">
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="type" xsi:type="string">status</item>
                        <item name="label" xsi:type="string" translate="true">Change status</item>
                    </item>
                </argument>
                <argument name="actions" xsi:type="array">
                    <item name="0" xsi:type="array">
                        <item name="type" xsi:type="string">enable</item>
                        <item name="label" xsi:type="string" translate="true">Enable</item>
                        <item name="url" xsi:type="url" path="magelearn_productsgrid/tag/massStatus">
                            <param name="status">1</param>
                        </item>
                    </item>
                    <item name="1" xsi:type="array">
                        <item name="type" xsi:type="string">disable</item>
                        <item name="label" xsi:type="string" translate="true">Disable</item>
                        <item name="url" xsi:type="url" path="magelearn_productsgrid/tag/massStatus">
                            <param name="status">0</param>
                        </item>
                    </item>
                </argument>
            </action>
        </massaction>
        <paging name="listing_paging"/>
    </listingToolbar>
    <columns name="magelearn_item_tag_columns">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="editorConfig" xsi:type="array">
                    <item name="selectProvider" xsi:type="string">magelearn_item_tag_listing.magelearn_item_tag_listing.magelearn_item_tag_columns.ids</item>
                    <item name="enabled" xsi:type="boolean">true</item>
                    <item name="indexField" xsi:type="string">tag_id</item>
                    <item name="clientConfig" xsi:type="array">
                        <item name="saveUrl" xsi:type="url" path="magelearn_productsgrid/tag/inlineEdit"/>
                        <item name="validateBeforeSave" xsi:type="boolean">false</item>
                    </item>
                </item>
                <item name="childDefaults" xsi:type="array">
                    <item name="fieldAction" xsi:type="array">
                        <item name="provider" xsi:type="string">magelearn_item_tag_listing.magelearn_item_tag_listing.magelearn_item_tag_columns_editor</item>
                        <item name="target" xsi:type="string">startEdit</item>
                        <item name="params" xsi:type="array">
                            <item name="0" xsi:type="string">${ $.$data.rowIndex }</item>
                            <item name="1" xsi:type="boolean">true</item>
                        </item>
                    </item>
                </item>
            </item>
        </argument>
        <selectionsColumn name="ids">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="resizeEnabled" xsi:type="boolean">false</item>
                    <item name="resizeDefaultWidth" xsi:type="string">55</item>
                    <item name="indexField" xsi:type="string">tag_id</item>
                </item>
            </argument>
        </selectionsColumn>
        <column name="tag_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>
            </argument>
        </column>
        <column name="name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">text</item>
                    <item name="editor" xsi:type="array">
                        <item name="editorType" xsi:type="string">text</item>
                        <item name="validation" xsi:type="array">
                            <item name="required-entry" xsi:type="boolean">true</item>
                        </item>
                    </item>
                    <item name="label" xsi:type="string" translate="true">Name</item>
                </item>
            </argument>
        </column>
        <column name="enabled">
            <argument name="data" xsi:type="array">
                <item name="options" xsi:type="object">Magento\Config\Model\Config\Source\Enabledisable</item>
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">select</item>
                    <item name="editor" xsi:type="string">select</item>
                    <item name="label" xsi:type="string" translate="true">Status</item>
                    <item name="component" xsi:type="string">Magelearn_ProductsGrid/js/grid/columns/enable</item>
                    <item name="dataType" xsi:type="string">select</item>
                </item>
            </argument>
        </column>
        <column name="created_at" 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</item>
                </item>
            </argument>
        </column>
        <column name="updated_at" 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">Modified</item>
                    <item name="visible" xsi:type="boolean">false</item>
                </item>
            </argument>
        </column>
        <actionsColumn name="actions" class="Magelearn\ProductsGrid\Ui\Component\Listing\Column\TagActions">
            <settings>
                <indexField>tag_id</indexField>
                <resizeEnabled>false</resizeEnabled>
                <resizeDefaultWidth>107</resizeDefaultWidth>
            </settings>
        </actionsColumn>
    </columns>
</listing>

As per the highlighted code above, we will add mass action files.

Add file at Controller/Adminhtml/Tag/MassDelete.php file.

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Tag;

use Exception;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Backend\Model\View\Result\Redirect;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Ui\Component\MassAction\Filter;
use Magelearn\ProductsGrid\Model\ResourceModel\Tag\CollectionFactory;

/**
 * Class MassDelete
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Tag
 */
class MassDelete extends Action
{
    /**
     * @var Filter
     */
    public $filter;

    /**
     * @var CollectionFactory
     */
    public $collectionFactory;

    /**
     * MassDelete constructor.
     *
     * @param Context $context
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(
        Context $context,
        Filter $filter,
        CollectionFactory $collectionFactory
    ) {
        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;

        parent::__construct($context);
    }

    /**
     * @return $this|ResponseInterface|ResultInterface
     * @throws LocalizedException
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());

        try {
            $collection->walk('delete');
            $this->messageManager->addSuccessMessage(__('Tags has been deleted.'));
        } catch (Exception $e) {
            $this->messageManager->addSuccessMessage(__('Something wrong when delete Tag.'));
        }

        /** @var Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);

        return $resultRedirect->setPath('*/*/');
    }
}

Add file at Controller/Adminhtml/Tag/MassStatus.php file.

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Tag;

use Exception;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Backend\Model\View\Result\Redirect;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Ui\Component\MassAction\Filter;
use Magelearn\ProductsGrid\Model\ResourceModel\Tag\CollectionFactory;

/**
 * Class MassStatus
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Tag
 */
class MassStatus extends Action
{
    /**
     * Mass Action Filter
     *
     * @var Filter
     */
    public $filter;

    /**
     * Collection Factory
     *
     * @var \Magelearn\ProductsGrid\Model\ResourceModel\Tag\CollectionFactory
     */
    public $collectionFactory;

    /**
     * MassStatus constructor.
     *
     * @param Context $context
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(
        Context $context,
        Filter $filter,
        CollectionFactory $collectionFactory
    ) {
        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;

        parent::__construct($context);
    }

    /**
     * @return $this|ResponseInterface|ResultInterface
     * @throws LocalizedException
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());
        $status = (int)$this->getRequest()->getParam('status');

        $tagUpdated = 0;
        foreach ($collection as $tag) {
            try {
                $tag->setEnabled($status)
                    ->save();

                $tagUpdated++;
            } catch (LocalizedException $e) {
                $this->messageManager->addErrorMessage($e->getMessage());
            } catch (Exception $e) {
                $this->_getSession()->addException(
                    $e,
                    __('Something went wrong while updating status for %1.', $tag->getName())
                );
            }
        }

        if ($tagUpdated) {
            $this->messageManager->addSuccessMessage(__('A total of %1 record(s) have been updated.', $tagUpdated));
        }

        /** @var Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);

        return $resultRedirect->setPath('*/*/');
    }
}

Add file at Controller/Adminhtml/Tag/InlineEdit.php

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Tag;

use Exception;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\Result\Json;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\LocalizedException;
use Magelearn\ProductsGrid\Model\Tag;
use Magelearn\ProductsGrid\Model\TagFactory;
use RuntimeException;

/**
 * Class InlineEdit
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Tag
 */
class InlineEdit extends Action
{
    /**
     * @var JsonFactory
     */
    public $jsonFactory;

    /**
     * @var TagFactory
     */
    public $tagFactory;

    /**
     * InlineEdit constructor.
     *
     * @param Context $context
     * @param JsonFactory $jsonFactory
     * @param TagFactory $tagFactory
     */
    public function __construct(
        Context $context,
        JsonFactory $jsonFactory,
        TagFactory $tagFactory
    ) {
        $this->jsonFactory = $jsonFactory;
        $this->tagFactory = $tagFactory;

        parent::__construct($context);
    }

    /**
     * @return ResultInterface
     */
    public function execute()
    {
        /** @var Json $resultJson */
        $resultJson = $this->jsonFactory->create();
        $error = false;
        $messages = [];
        $postItems = $this->getRequest()->getParam('items', []);
        if (!($this->getRequest()->getParam('isAjax') && !empty($postItems))) {
            return $resultJson->setData([
                'messages' => [__('Please correct the data sent.')],
                'error' => true,
            ]);
        }

        $key = array_keys($postItems);
        $tagId = !empty($key) ? (int)$key[0] : '';
        /** @var Tag $tag */
        $tag = $this->tagFactory->create()->load($tagId);
        try {
            $tag->addData($postItems[$tagId])
                ->save();
        } catch (LocalizedException $e) {
            $messages[] = $this->getErrorWithTagId($tag, $e->getMessage());
            $error = true;
        } catch (RuntimeException $e) {
            $messages[] = $this->getErrorWithTagId($tag, $e->getMessage());
            $error = true;
        } catch (Exception $e) {
            $messages[] = $this->getErrorWithTagId($tag, __('Something went wrong while saving the Tag.'));
            $error = true;
        }

        return $resultJson->setData([
            'messages' => $messages,
            'error' => $error
        ]);
    }

    /**
     * Add Tag id to error message
     *
     * @param Tag $tag
     * @param string $errorText
     *
     * @return string
     */
    public function getErrorWithTagId(Tag $tag, $errorText)
    {
        return '[Tag ID: ' . $tag->getId() . '] ' . $errorText;
    }
}

Now we will add our action files at Ui/Component/Listing/Column/TagActions.php

<?php

namespace Magelearn\ProductsGrid\Ui\Component\Listing\Column;

use Magento\Framework\UrlInterface;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Ui\Component\Listing\Columns\Column;

/**
 * Class Actions
 * @package Magelearn\ProductsGrid\Ui\Component\Listing\Columns
 */
class TagActions extends Column
{
    const URL_PATH_EDIT = 'magelearn_productsgrid/tag/edit';
    const URL_PATH_DELETE = 'magelearn_productsgrid/tag/delete';

    /**
     * @var UrlInterface
     */
    protected $urlBuilder;

    /**
     * Constructor
     *
     * @param ContextInterface $context
     * @param UiComponentFactory $uiComponentFactory
     * @param UrlInterface $urlBuilder
     * @param array $components
     * @param array $data
     */
    public function __construct(
        ContextInterface $context,
        UiComponentFactory $uiComponentFactory,
        UrlInterface $urlBuilder,
        array $components = [],
        array $data = []
    ) {
        $this->urlBuilder = $urlBuilder;

        parent::__construct($context, $uiComponentFactory, $components, $data);
    }

    /**
     * Prepare Data Source
     *
     * @param array $dataSource
     *
     * @return array
     */
    public function prepareDataSource(array $dataSource)
    {
        if (isset($dataSource['data']['items'])) {
            foreach ($dataSource['data']['items'] as &$item) {
                $item[$this->getData('name')] = [
                    'edit' => [
                        'href' => $this->urlBuilder->getUrl(
                            static::URL_PATH_EDIT,
                            [
                                'id' => $item['tag_id']
                            ]
                            ),
                        'label' => __('Edit')
                    ],
                    'delete' => [
                        'href' => $this->urlBuilder->getUrl(
                            static::URL_PATH_DELETE,
                            [
                                'id' => $item['tag_id']
                            ]
                            ),
                        'label' => __('Delete'),
                        'confirm' => [
                            'title' => __('Delete %1',$item['tag_id']),
                            'message' => __('Are you sure you wan\'t to delete a %1 record ?',$item['tag_id'])
                        ]
                    ]
                ];
            }
        }

        return $dataSource;
    }
}

To Provide a Delete action, add file at Controller/Adminhtml/Tag/Delete.php file.

<?php
declare(strict_types=1);
 
namespace Magelearn\ProductsGrid\Controller\Adminhtml\Tag;

use Magento\Backend\App\Action\Context;
use Magento\Framework\Registry;
use Magento\Framework\View\Result\Page;
use Magento\Framework\View\Result\PageFactory;
use Magelearn\ProductsGrid\Controller\Adminhtml\Tag;
use Magelearn\ProductsGrid\Model\TagFactory;
 
class Delete extends Tag
{
    /**
     * Tag Factory
     *
     * @var TagFactory
     */
    public $tagFactory;
    
    /**
     * Page factory
     *
     * @var PageFactory
     */
    public $resultPageFactory;
    
    /**
     * Delete constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param TagFactory $tagFactory
     * @param PageFactory $resultPageFactory
     */
    public function __construct(
        Context $context,
        Registry $coreRegistry,
        TagFactory $tagFactory,
        PageFactory $resultPageFactory
        ) {
            $this->resultPageFactory = $resultPageFactory;
            
            parent::__construct($context, $coreRegistry, $tagFactory);
    }
 
    /**
     * Delete action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultRedirectFactory->create();
        // check if we know what should be deleted
        $id = $this->getRequest()->getParam('id');
        if ($id) {
            try {
                // init model and delete
                $model = $this->initTag();
                $model->delete();
                // display success message
                $this->messageManager->addSuccessMessage(__('You deleted the Tag.'));
                // go to grid
                return $resultRedirect->setPath('*/*/');
            } catch (\Exception $e) {
                // display error message
                $this->messageManager->addErrorMessage($e->getMessage());
                // go back to edit form
                return $resultRedirect->setPath('*/*/edit', ['id' => $id]);
            }
        }
        // display error message
        $this->messageManager->addErrorMessage(__('We can\'t find a Tag to delete.'));
        // go to grid
        return $resultRedirect->setPath('*/*/');
    }
}

As per the highlighted code above, add file at Controller/Adminhtml/Tag.php

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Registry;
use Magelearn\ProductsGrid\Model\TagFactory;

/**
 * Class Tag
 * @package Magelearn\ProductsGrid\Controller\Adminhtml
 */
abstract class Tag extends Action
{
    /** Authorization level of a basic admin session */
    const ADMIN_RESOURCE = 'Magelearn_ProductsGrid::tag';

    /**
     * Tag Factory
     *
     * @var TagFactory
     */
    public $tagFactory;

    /**
     * Core registry
     *
     * @var Registry
     */
    public $coreRegistry;

    /**
     * Tag constructor.
     *
     * @param Context $context
     * @param Registry $coreRegistry
     * @param TagFactory $tagFactory
     */
    public function __construct(
        Context $context,
        Registry $coreRegistry,
        TagFactory $tagFactory
    ) {
        $this->tagFactory = $tagFactory;
        $this->coreRegistry = $coreRegistry;

        parent::__construct($context);
    }

    /**
     * @param bool $register
     *
     * @return bool|\Magelearn\ProductsGrid\Model\Tag
     */
    protected function initTag($register = false)
    {
        $tagId = (int)$this->getRequest()->getParam('id');

        /** @var \Magelearn\ProductsGrid\Model\Tag $tag */
        $tag = $this->tagFactory->create();
        if ($tagId) {
            $tag->load($tagId);
            if (!$tag->getId()) {
                $this->messageManager->addErrorMessage(__('This tag no longer exists.'));

                return false;
            }
        }

        if ($register) {
            $this->coreRegistry->register('magelearn_item_tag', $tag);
        }

        return $tag;
    }
}

Now to add a new action, we will add Controller/Adminhtml/Tag/NewAction.php file.

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Tag;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Backend\Model\View\Result\Forward;
use Magento\Backend\Model\View\Result\ForwardFactory;

/**
 * Class NewAction
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Tag
 */
class NewAction extends Action
{
    /**
     * @var ForwardFactory
     */
    public $resultForwardFactory;

    /**
     * NewAction constructor.
     *
     * @param ForwardFactory $resultForwardFactory
     * @param Context $context
     */
    public function __construct(
        ForwardFactory $resultForwardFactory,
        Context $context
    ) {
        $this->resultForwardFactory = $resultForwardFactory;

        parent::__construct($context);
    }

    /**
     * forward to edit
     *
     * @return Forward
     */
    public function execute()
    {
        $resultForward = $this->resultForwardFactory->create();
        $resultForward->forward('edit');

        return $resultForward;
    }
}

This will redirect to the Edit action. For that add a file at Controller/Adminhtml/Tag/Edit.php

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Tag;

use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\Result\Redirect;
use Magento\Framework\Registry;
use Magento\Framework\View\Result\Page;
use Magento\Framework\View\Result\PageFactory;
use Magelearn\ProductsGrid\Controller\Adminhtml\Tag;
use Magelearn\ProductsGrid\Model\TagFactory;

/**
 * Class Edit
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Tag
 */
class Edit extends Tag
{
    /**
     * Page factory
     *
     * @var PageFactory
     */
    public $resultPageFactory;

    /**
     * Edit constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param TagFactory $tagFactory
     * @param PageFactory $resultPageFactory
     */
    public function __construct(
        Context $context,
        Registry $registry,
        TagFactory $tagFactory,
        PageFactory $resultPageFactory
    ) {
        $this->resultPageFactory = $resultPageFactory;

        parent::__construct($context, $registry, $tagFactory);
    }

    /**
     * @return \Magento\Backend\Model\View\Result\Page|Redirect|Page
     */
    public function execute()
    {
        /** @var \Magelearn\ProductsGrid\Model\Tag $tag */
        $tag = $this->initTag();
        if (!$tag) {
            $resultRedirect = $this->resultRedirectFactory->create();
            $resultRedirect->setPath('*');

            return $resultRedirect;
        }

        $data = $this->_session->getData('magelearn_item_tag_data', true);
        if (!empty($data)) {
            $tag->setData($data);
        }

        $this->coreRegistry->register('magelearn_item_tag', $tag);

        /** @var \Magento\Backend\Model\View\Result\Page|Page $resultPage */
        $resultPage = $this->resultPageFactory->create();
        $resultPage->setActiveMenu('Magelearn_ProductsGrid::tag');
        $resultPage->getConfig()->getTitle()->set(__('Tags'));

        $title = $tag->getId() ? $tag->getName() : __('New Tag');
        $resultPage->getConfig()->getTitle()->prepend($title);

        return $resultPage;
    }
}

Add layout file at view/adminhtml/layout/magelearn_productsgrid_tag_edit.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="editor"/>
    <body>
        <referenceContainer name="content">
            <block class="Magelearn\ProductsGrid\Block\Adminhtml\Tag\Edit" name="magelearn_item_tag_edit"/>
        </referenceContainer>
        <referenceContainer name="left">
            <block class="Magelearn\ProductsGrid\Block\Adminhtml\Tag\Edit\Tabs" name="magelearn_item_tag_tabs">
                <block class="Magelearn\ProductsGrid\Block\Adminhtml\Tag\Edit\Tab\Tag" name="magelearn_item_tag_edit_tab_tag"/>
                <block class="Magelearn\ProductsGrid\Block\Adminhtml\Tag\Edit\Tab\Post" name="magelearn_item_tag_edit_tab_post"/>
                <action method="addTab">
                    <argument name="name" xsi:type="string">tag</argument>
                    <argument name="block" xsi:type="string">magelearn_item_tag_edit_tab_tag</argument>
                </action>
                <action method="addTab">
                    <argument name="name" xsi:type="string">post</argument>
                    <argument name="block" xsi:type="string">magelearn_item_tag_edit_tab_post</argument>
                </action>
            </block>
        </referenceContainer>
    </body>
</page>

As per the highlighted code above add the file at Block/Adminhtml/Tag/Edit.php

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Tag;

use Magento\Backend\Block\Widget\Context;
use Magento\Backend\Block\Widget\Form\Container;
use Magento\Framework\Registry;
use Magelearn\ProductsGrid\Model\Tag;

/**
 * Class Edit
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Tag
 */
class Edit extends Container
{
    /**
     * @var Registry
     */
    public $coreRegistry;

    /**
     * Edit constructor.
     *
     *@param Registry $coreRegistry
     * @param Context $context
     * @param array $data
     */
    public function __construct(
        Registry $coreRegistry,
        Context $context,
        array $data = []
    ) {
        $this->coreRegistry = $coreRegistry;

        parent::__construct($context, $data);
    }

    /**
     * Initialize Tag edit block
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_blockGroup = 'Magelearn_ProductsGrid';
        $this->_controller = 'adminhtml_tag';

        parent::_construct();

        $this->buttonList->add(
            'save-and-continue',
            [
                'label' => __('Save and Continue Edit'),
                'class' => 'save',
                'data_attribute' => [
                    'mage-init' => [
                        'button' => [
                            'event' => 'saveAndContinueEdit',
                            'target' => '#edit_form'
                        ]
                    ]
                ]
            ],
            -100
        );
    }

    /**
     * Retrieve text for header element depending on loaded Tag
     *
     * @return string
     */
    public function getHeaderText()
    {
        /** @var Tag $tag */
        $tag = $this->coreRegistry->registry('magelearn_item_tag');
        if ($tag->getId()) {
            return __("Edit Tag '%1'", $this->escapeHtml($tag->getName()));
        }

        return __('New Tag');
    }
}

Add file at Block/Adminhtml/Tag/Edit/Form.php

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Tag\Edit;

use Magento\Backend\Block\Widget\Form\Generic;

/**
 * Class Form
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Tag\Edit
 */
class Form extends Generic
{
    /**
     * @inheritdoc
     */
    protected function _prepareForm()
    {
        /** @var \Magento\Framework\Data\Form $form */
        $form = $this->_formFactory->create([
            'data' => [
                'id' => 'edit_form',
                'action' => $this->getData('action'),
                'method' => 'post',
                'enctype' => 'multipart/form-data'
            ]
        ]);

        $form->setUseContainer(true);
        $this->setForm($form);

        return parent::_prepareForm();
    }
}

Add file at Block/Adminhtml/Tag/Edit/Tabs.php

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Tag\Edit;

/**
 * Class Tabs
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Tag\Edit
 */
class Tabs extends \Magento\Backend\Block\Widget\Tabs
{
    /**
     * constructor
     *
     * @return void
     */
    protected function _construct()
    {
        parent::_construct();
        $this->setId('tag_tabs');
        $this->setDestElementId('edit_form');
        $this->setTitle(__('Tag Information'));
    }
}

Add file at Block/Adminhtml/Tag/Edit/Tab/Tag.php

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Tag\Edit\Tab;

use Magento\Backend\Block\Store\Switcher\Form\Renderer\Fieldset\Element;
use Magento\Backend\Block\Template\Context;
use Magento\Backend\Block\Widget\Form\Generic;
use Magento\Backend\Block\Widget\Tab\TabInterface;
use Magento\Cms\Model\Wysiwyg\Config;
use Magento\Config\Model\Config\Source\Enabledisable;
use Magento\Config\Model\Config\Source\Yesno;
use Magento\Framework\Data\Form\Element\Renderer\RendererInterface;
use Magento\Framework\Data\FormFactory;
use Magento\Framework\Registry;

/**
 * Class Tag
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Tag\Edit\Tab
 */
class Tag extends Generic implements TabInterface
{
    /**
     * Wysiwyg config
     *
     * @var Config
     */
    public $wysiwygConfig;

    /**
     * Country options
     *
     * @var Yesno
     */
    public $booleanOptions;

    /**
     * @var Enabledisable
     */
    protected $enableDisable;

    /**
     * Tag constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param FormFactory $formFactory
     * @param Config $wysiwygConfig
     * @param Yesno $booleanOptions
     * @param Enabledisable $enableDisable
     * @param array $data
     */
    public function __construct(
        Context $context,
        Registry $registry,
        FormFactory $formFactory,
        Config $wysiwygConfig,
        Yesno $booleanOptions,
        Enabledisable $enableDisable,
        array $data = []
    ) {
        $this->wysiwygConfig = $wysiwygConfig;
        $this->booleanOptions = $booleanOptions;
        $this->enableDisable = $enableDisable;

        parent::__construct($context, $registry, $formFactory, $data);
    }

    /**
     * @inheritdoc
     */
    protected function _prepareForm()
    {
        /** @var \Magelearn\ProductsGrid\Model\Tag $tag */
        $tag = $this->_coreRegistry->registry('magelearn_item_tag');

        $form = $this->_formFactory->create();
        $form->setHtmlIdPrefix('tag_');
        $form->setFieldNameSuffix('tag');

        $fieldset = $form->addFieldset('base_fieldset', [
            'legend' => __('Tag Information'),
            'class' => 'fieldset-wide'
        ]);
        if ($tag->getId()) {
            $fieldset->addField('tag_id', 'hidden', ['name' => 'tag_id']);
        }

        $fieldset->addField('name', 'text', [
            'name' => 'name',
            'label' => __('Name'),
            'title' => __('Name'),
            'required' => true,
        ]);
        
        $fieldset->addField('enabled', 'select', [
            'name' => 'enabled',
            'label' => __('Status'),
            'title' => __('Status'),
            'values' => $this->enableDisable->toOptionArray(),
        ]);
        if (!$tag->hasData('enabled')) {
            $tag->setEnabled(1);
        }
        
        $fieldset->addField('description', 'editor', [
            'name' => 'description',
            'label' => __('Description'),
            'title' => __('Description'),
            'config' => $this->wysiwygConfig->getConfig(['add_variables' => false, 'add_widgets' => false])
        ]);

        $form->addValues($tag->getData());
        $this->setForm($form);

        return parent::_prepareForm();
    }

    /**
     * Prepare label for tab
     *
     * @return string
     */
    public function getTabLabel()
    {
        return __('Tag');
    }

    /**
     * Prepare title for tab
     *
     * @return string
     */
    public function getTabTitle()
    {
        return $this->getTabLabel();
    }

    /**
     * Can show tab in tabs
     *
     * @return boolean
     */
    public function canShowTab()
    {
        return true;
    }

    /**
     * Tab is hidden
     *
     * @return boolean
     */
    public function isHidden()
    {
        return false;
    }
}

Add file at Block/Adminhtml/Tag/Edit/Tab/Post.php

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Tag\Edit\Tab;

use Exception;
use Magento\Backend\Block\Template\Context;
use Magento\Backend\Block\Widget\Grid\Column;
use Magento\Backend\Block\Widget\Grid\Extended;
use Magento\Backend\Block\Widget\Tab\TabInterface;
use Magento\Backend\Helper\Data;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Registry;
use Magelearn\ProductsGrid\Model\PostFactory;
use Magelearn\ProductsGrid\Model\ResourceModel\Post\Collection;
use Magelearn\ProductsGrid\Model\ResourceModel\Post\CollectionFactory;

/**
 * Class Post
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Tag\Edit\Tab
 */
class Post extends Extended implements TabInterface
{
    /**
     * Post collection factory
     *
     * @var CollectionFactory
     */
    public $postCollectionFactory;

    /**
     * Registry
     *
     * @var Registry
     */
    public $coreRegistry;

    /**
     * Post factory
     *
     * @var PostFactory
     */
    public $postFactory;

    /**
     * Post constructor.
     *
     * @param Context $context
     * @param Registry $coreRegistry
     * @param Data $backendHelper
     * @param PostFactory $postFactory
     * @param CollectionFactory $postCollectionFactory
     * @param array $data
     */
    public function __construct(
        Context $context,
        Registry $coreRegistry,
        Data $backendHelper,
        PostFactory $postFactory,
        CollectionFactory $postCollectionFactory,
        array $data = []
    ) {
        $this->coreRegistry          = $coreRegistry;
        $this->postFactory           = $postFactory;
        $this->postCollectionFactory = $postCollectionFactory;

        parent::__construct($context, $backendHelper, $data);
    }

    /**
     * Set grid params
     */
    public function _construct()
    {
        parent::_construct();
        $this->setId('post_grid');
        $this->setDefaultSort('position');
        $this->setDefaultDir('ASC');
        $this->setSaveParametersInSession(false);
        $this->setUseAjax(true);

        if ($this->getTag()->getId()) {
            $this->setDefaultFilter(['in_posts' => 1]);
        }
    }

    /**
     * @inheritdoc
     */
    protected function _prepareCollection()
    {
        /** @var Collection $collection */
        $collection = $this->postCollectionFactory->create();
        $collection->getSelect()->joinLeft(
            ['related' => $collection->getTable('magelearn_item_post_tag')],
            'related.item_id=main_table.item_id AND related.tag_id=' . (int) $this->getRequest()->getParam('id', 0),
            ['position']
        );

        $collection->addFilterToMap('item_id', 'main_table.item_id');

        $this->setCollection($collection);

        return parent::_prepareCollection();
    }

    /**
     * @return $this
     * @throws Exception
     */
    protected function _prepareColumns()
    {
        $this->addColumn('in_posts', [
            'header_css_class' => 'a-center',
            'type'             => 'checkbox',
            'name'             => 'in_post',
            'values'           => $this->_getSelectedPosts(),
            'align'            => 'center',
            'index'            => 'item_id'
        ]);
        $this->addColumn('item_id', [
            'header'           => __('ID'),
            'sortable'         => true,
            'index'            => 'item_id',
            'type'             => 'number',
            'header_css_class' => 'col-id',
            'column_css_class' => 'col-id'
        ]);
        $this->addColumn('title', [
            'header'           => __('Name'),
            'index'            => 'name',
            'header_css_class' => 'col-name',
            'column_css_class' => 'col-name'
        ]);
        
        $this->addColumn('position', [
            'header'         => __('Position'),
            'name'           => 'position',
            'width'          => 60,
            'type'           => 'number',
            'validate_class' => 'validate-number',
            'index'          => 'position',
            'editable'       => true
        ]);

        return $this;
    }

    /**
     * Retrieve selected Posts
     * @return array
     */
    protected function _getSelectedPosts()
    {
        $posts = $this->getRequest()->getPost('tag_posts', null);
        if (!is_array($posts)) {
            $posts = $this->getTag()->getPostsPosition();

            return array_keys($posts);
        }

        return $posts;
    }

    /**
     * Retrieve selected Posts
     * @return array
     */
    public function getSelectedPosts()
    {
        $selected = $this->getTag()->getPostsPosition();
        if (!is_array($selected)) {
            $selected = [];
        } else {
            foreach ($selected as $key => $value) {
                $selected[$key] = ['position' => $value];
            }
        }

        return $selected;
    }

    /**
     * @param \Magelearn\ProductsGrid\Model\Post|Object $item
     *
     * @return string
     */
    public function getRowUrl($item)
    {
        return '#';
    }

    /**
     * get grid url
     *
     * @return string
     */
    public function getGridUrl()
    {
        return $this->getUrl('*/*/postsGrid', ['id' => $this->getTag()->getId()]);
    }

    /**
     * @return \Magelearn\ProductsGrid\Model\Tag
     */
    public function getTag()
    {
        return $this->coreRegistry->registry('magelearn_item_tag');
    }

    /**
     * @param Column $column
     *
     * @return $this
     * @throws LocalizedException
     */
    protected function _addColumnFilterToCollection($column)
    {
        if ($column->getId() == 'in_posts') {
            $postIds = $this->_getSelectedPosts();
            if (empty($postIds)) {
                $postIds = 0;
            }
            if ($column->getFilter()->getValue()) {
                $this->getCollection()->addFieldToFilter('main_table.item_id', ['in' => $postIds]);
            } elseif ($postIds) {
                $this->getCollection()->addFieldToFilter('main_table.item_id', ['nin' => $postIds]);
            }
        } else {
            parent::_addColumnFilterToCollection($column);
        }

        return $this;
    }

    /**
     * @return string
     */
    public function getTabLabel()
    {
        return __('Posts');
    }

    /**
     * @return bool
     */
    public function isHidden()
    {
        return false;
    }

    /**
     * @return string
     */
    public function getTabTitle()
    {
        return $this->getTabLabel();
    }

    /**
     * @return bool
     */
    public function canShowTab()
    {
        return true;
    }

    /**
     * @return string
     */
    public function getTabUrl()
    {
        return $this->getUrl('magelearn_productsgrid/tag/posts', ['_current' => true]);
    }

    /**
     * @return string
     */
    public function getTabClass()
    {
        return 'ajax only';
    }
}

Now as per the highlighted code above we will add Controller/Adminhtml/Tag/Posts.php

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Tag;

use Magento\Backend\App\Action\Context;
use Magento\Framework\Registry;
use Magento\Framework\View\Result\Layout;
use Magento\Framework\View\Result\LayoutFactory;
use Magelearn\ProductsGrid\Controller\Adminhtml\Tag;
use Magelearn\ProductsGrid\Model\TagFactory;

/**
 * Class Posts
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Tag
 */
class Posts extends Tag
{
    /**
     * @var LayoutFactory
     */
    public $resultLayoutFactory;

    /**
     * Posts constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param LayoutFactory $resultLayoutFactory
     * @param TagFactory $postFactory
     */
    public function __construct(
        Context $context,
        Registry $registry,
        LayoutFactory $resultLayoutFactory,
        TagFactory $postFactory
    ) {
        $this->resultLayoutFactory = $resultLayoutFactory;

        parent::__construct($context, $registry, $postFactory);
    }

    /**
     * @return Layout
     */
    public function execute()
    {
        $this->initTag(true);

        return $this->resultLayoutFactory->create();
    }
}

And Controller/Adminhtml/Tag/PostsGrid.php file.

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Tag;

/**
 * Class PostsGrid
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Tag
 */
class PostsGrid extends Posts
{
}

Also, add a layout file at view/adminhtml/layout/magelearn_productsgrid_tag_posts.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" output="1">
        <block class="Magelearn\ProductsGrid\Block\Adminhtml\Tag\Edit\Tab\Post" name="tag.edit.tab.post"/>
        <block class="Magento\Backend\Block\Widget\Grid\Serializer" name="post_grid_serializer">
            <arguments>
                <argument name="input_names" xsi:type="string">position</argument>
                <argument name="grid_block" xsi:type="string">tag.edit.tab.post</argument>
                <argument name="callback" xsi:type="string">getSelectedPosts</argument>
                <argument name="input_element_name" xsi:type="string">posts</argument>
                <argument name="reload_param_name" xsi:type="string">tag_posts</argument>
            </arguments>
        </block>
    </container>
</layout>

And view/adminhtml/layout/magelearn_productsgrid_tag_postsgrid.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\ProductsGrid\Block\Adminhtml\Tag\Edit\Tab\Post" name="tag.edit.tab.post"/>
    </container>
</layout>

Post Management

Add file at Controller/Adminhtml/Post/Index.php
<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);
 
namespace Magelearn\ProductsGrid\Controller\Adminhtml\Post;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\View\Result\Page;
use Magento\Framework\View\Result\PageFactory;
 
class Index extends Action
{
    /**
     * Page result factory
     *
     * @var PageFactory
     */
    public $resultPageFactory;
 
    /**
     * Index Constructor
     *
     * @param Context $context
     * @param PageFactory $resultPageFactory
     */
    public function __construct(
        Context $context,
        PageFactory $resultPageFactory
    ) {
        $this->resultPageFactory = $resultPageFactory;
        parent::__construct($context);
    }
 
    /**
     * Index action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        $resultPage = $this->resultPageFactory->create();
        $resultPage->getConfig()->getTitle()->prepend(__("Posts"));

        return $resultPage;
    }
}

Add layout file at view/adminhtml/layout/magelearn_productsgrid_post_index.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="styles"/>
    <body>
        <referenceBlock name="menu">
            <action method="setActive">
                <argument name="itemId" xsi:type="string">Magelearn_ProductsGrid::post</argument>
            </action>
        </referenceBlock>
        <referenceBlock name="page.title">
            <action method="setTitleClass">
                <argument name="class" xsi:type="string">complex</argument>
            </action>
        </referenceBlock>
        <referenceContainer name="content">
            <uiComponent name="magelearn_item_post_listing"/>
        </referenceContainer>
    </body>
</page>

As per the highlighted code above add uiComponent file at view/adminhtml/ui_component/magelearn_item_post_listing.xml

<?xml version="1.0"?>
<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">magelearn_item_post_listing.magelearn_item_post_listing_data_source</item>
            <item name="deps" xsi:type="string">magelearn_item_post_listing.magelearn_item_post_listing_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">magelearn_item_post_columns</item>
        <item name="buttons" xsi:type="array">
            <item name="add" xsi:type="array">
                <item name="name" xsi:type="string">add</item>
                <item name="label" xsi:type="string" translate="true">Add New Post</item>
                <item name="class" xsi:type="string">primary</item>
                <item name="url" xsi:type="string">*/*/new</item>
            </item>
        </item>
    </argument>
    <dataSource name="magelearn_item_post_listing_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider</argument>
            <argument name="name" xsi:type="string">magelearn_item_post_listing_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">item_id</argument>
            <argument name="requestFieldName" xsi:type="string">item_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="indexField" xsi:type="string">item_id</item>
                    </item>
                </item>
            </argument>
        </argument>
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
            </item>
        </argument>
    </dataSource>
    <listingToolbar name="listing_top">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="sticky" xsi:type="boolean">true</item>
            </item>
        </argument>
        <bookmark name="bookmarks"/>
        <columnsControls name="columns_controls"/>
        <exportButton name="export_button"/>
        <filters name="listing_filters"/>
        <massaction name="listing_massaction">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/tree-massactions</item>
                </item>
            </argument>
            <action name="delete">
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="type" xsi:type="string">delete</item>
                        <item name="label" xsi:type="string" translate="true">Delete</item>
                        <item name="url" xsi:type="url" path="magelearn_productsgrid/post/massDelete"/>
                        <item name="confirm" xsi:type="array">
                            <item name="title" xsi:type="string" translate="true">Delete Posts</item>
                            <item name="message" xsi:type="string" translate="true">Are you sure you wan't to delete selected Posts?</item>
                        </item>
                    </item>
                </argument>
            </action>
            <action name="status">
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="type" xsi:type="string">status</item>
                        <item name="label" xsi:type="string" translate="true">Change status</item>
                    </item>
                </argument>
                <argument name="actions" xsi:type="array">
                    <item name="0" xsi:type="array">
                        <item name="type" xsi:type="string">pending</item>
                        <item name="label" xsi:type="string" translate="true">Pending</item>
                        <item name="url" xsi:type="url" path="magelearn_productsgrid/post/massStatus">
                            <param name="status">0</param>
                        </item>
                    </item>
                    <item name="1" xsi:type="array">
                        <item name="type" xsi:type="string">approved</item>
                        <item name="label" xsi:type="string" translate="true">Approved</item>
                        <item name="url" xsi:type="url" path="magelearn_productsgrid/post/massStatus">
                            <param name="status">1</param>
                        </item>
                    </item>
                    <item name="2" xsi:type="array">
                        <item name="type" xsi:type="string">disapproved</item>
                        <item name="label" xsi:type="string" translate="true">Disapproved</item>
                        <item name="url" xsi:type="url" path="magelearn_productsgrid/post/massStatus">
                            <param name="status">2</param>
                        </item>
                    </item>
                </argument>
            </action>
        </massaction>
        <paging name="listing_paging"/>
    </listingToolbar>
    <columns name="magelearn_item_post_columns">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="editorConfig" xsi:type="array">
                    <item name="selectProvider" xsi:type="string">magelearn_item_post_listing.magelearn_item_post_listing.magelearn_item_post_columns.ids</item>
                    <item name="enabled" xsi:type="boolean">true</item>
                    <item name="indexField" xsi:type="string">item_id</item>
                    <item name="clientConfig" xsi:type="array">
                        <item name="saveUrl" xsi:type="url" path="magelearn_productsgrid/post/inlineEdit"/>
                        <item name="validateBeforeSave" xsi:type="boolean">false</item>
                    </item>
                </item>
                <item name="childDefaults" xsi:type="array">
                    <item name="fieldAction" xsi:type="array">
                        <item name="provider" xsi:type="string">magelearn_item_post_listing.magelearn_item_post_listing.magelearn_item_post_columns_editor</item>
                        <item name="target" xsi:type="string">startEdit</item>
                        <item name="params" xsi:type="array">
                            <item name="0" xsi:type="string">${ $.$data.rowIndex }</item>
                            <item name="1" xsi:type="boolean">true</item>
                        </item>
                    </item>
                </item>
            </item>
        </argument>
        <selectionsColumn name="ids">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="resizeEnabled" xsi:type="boolean">false</item>
                    <item name="resizeDefaultWidth" xsi:type="string">55</item>
                    <item name="indexField" xsi:type="string">item_id</item>
                </item>
            </argument>
        </selectionsColumn>
        <column name="item_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>
            </argument>
        </column>
        <column name="name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">text</item>
                    <item name="editor" xsi:type="array">
                        <item name="editorType" xsi:type="string">text</item>
                        <item name="validation" xsi:type="array">
                            <item name="required-entry" xsi:type="boolean">true</item>
                        </item>
                    </item>
                    <item name="label" xsi:type="string" translate="true">Name</item>
                </item>
            </argument>
        </column>
        <column name="enabled">
            <argument name="data" xsi:type="array">
                <item name="options" xsi:type="object">Magelearn\ProductsGrid\Model\Config\Source\PostStatus</item>
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">select</item>
                    <item name="label" xsi:type="string" translate="true">Status</item>
                    <item name="editor" xsi:type="string">select</item>
                    <item name="component" xsi:type="string">Magelearn_ProductsGrid/js/grid/columns/enable</item>
                    <item name="dataType" xsi:type="string">select</item>
                </item>
            </argument>
        </column>
        <column name="created_at" 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</item>
                    <item name="visible" xsi:type="boolean">true</item>
                </item>
            </argument>
        </column>
        <column name="updated_at" 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">Modified</item>
                    <item name="visible" xsi:type="boolean">true</item>
                </item>
            </argument>
        </column>
        <column name="image" class="Magelearn\ProductsGrid\Ui\Component\Listing\Column\PostImage">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="sortOrder" xsi:type="number">80</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/thumbnail</item>
                    <item name="sortable" xsi:type="boolean">false</item>
                    <item name="altField" xsi:type="string">name</item>
                    <item name="has_preview" xsi:type="string">1</item>
                    <item name="label" xsi:type="string" translate="true">Image</item>
                </item>
            </argument>
        </column>
        <actionsColumn name="actions" class="Magelearn\ProductsGrid\Ui\Component\Listing\Column\PostActions">
            <settings>
                <indexField>item_id</indexField>
                <resizeEnabled>false</resizeEnabled>
                <resizeDefaultWidth>107</resizeDefaultWidth>
            </settings>
        </actionsColumn>
    </columns>
</listing>

As per the highlighted code above add Ui/Component/Listing/Column/PostImage.php file.

<?php

namespace Magelearn\ProductsGrid\Ui\Component\Listing\Column;

use Magento\Backend\Model\UrlInterface;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\View\Asset\Repository;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Ui\Component\Listing\Columns\Column;
use Magelearn\ProductsGrid\Helper\Image;

class PostImage extends Column
{
    private $storeManager;

    /**
     * @var Repository
     */
    private $assetRepo;

    /**
     * @var UrlInterface
     */
    private $_backendUrl;
    
    /**
     * @var Image
     */
    protected $imageHelper;

    /**
     * GroupIcon constructor.
     *
     * @param ContextInterface $context
     * @param UiComponentFactory $uiComponentFactory
     * @param StoreManagerInterface $storeManager
     * @param Repository $assetRepo
     * @param UrlInterface $backendUrl
     * @param Image $imageHelper
     * @param array $components
     * @param array $data
     */
    public function __construct(
        ContextInterface $context,
        UiComponentFactory $uiComponentFactory,
        StoreManagerInterface $storeManager,
        Repository $assetRepo,
        UrlInterface $backendUrl,
        Image $imageHelper,
        array $components = [],
        array $data = []
    ) {
        parent::__construct($context, $uiComponentFactory, $components, $data);
        $this->storeManager = $storeManager;
        $this->assetRepo = $assetRepo;
        $this->_backendUrl = $backendUrl;
        $this->imageHelper = $imageHelper;
    }

    /**
     * Prepare Data Source
     *
     * @param array $dataSource
     * @return array
     * @throws NoSuchEntityException
     */
    public function prepareDataSource(array $dataSource)
    {
        if (isset($dataSource['data']['items'])) {
            $path = $this->storeManager->getStore()->getBaseUrl(
                \Magento\Framework\UrlInterface::URL_TYPE_MEDIA
            );
            $baseImage = $this->assetRepo->getUrl('Magelearn_ProductsGrid::images/post_dummy.png');
            $fieldName = $this->getData('name');
            foreach ($dataSource['data']['items'] as & $item) {
                if ($item[$fieldName]) {
                    $item[$fieldName . '_src'] = $path . $this->imageHelper->getBaseMediaPath(Image::TEMPLATE_MEDIA_TYPE_POST) .'/'. $item['image'];
                    $item[$fieldName . '_alt'] = $item['name'];
                    $item[$fieldName . '_orig_src'] = $path . $this->imageHelper->getBaseMediaPath(Image::TEMPLATE_MEDIA_TYPE_POST) .'/'. $item['image'];
                } else {
                    $item[$fieldName . '_src'] = $baseImage;
                    $item[$fieldName . '_alt'] = 'Post Image';
                    $item[$fieldName . '_orig_src'] = $baseImage;
                }
                $item[$fieldName . '_link'] = $this->_backendUrl->getUrl(
                    "magelearn_productsgrid/post/edit",
                    ['id' => $item['item_id']]
                );
            }
        }

        return $dataSource;
    }
}

Now we will add our mass action files.

Add file at Controller/Adminhtml/Post/MassDelete.php

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Post;

use Exception;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Backend\Model\View\Result\Redirect;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Ui\Component\MassAction\Filter;
use Magelearn\ProductsGrid\Model\ResourceModel\Post\CollectionFactory;

/**
 * Class MassDelete
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Post
 */
class MassDelete extends Action
{
    /**
     * Mass Action Filter
     *
     * @var Filter
     */
    public $filter;

    /**
     * Collection Factory
     *
     * @var CollectionFactory
     */
    public $collectionFactory;

    /**
     * constructor
     *
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     * @param Context $context
     */
    public function __construct(
        Context $context,
        Filter $filter,
        CollectionFactory $collectionFactory
    ) {
        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;

        parent::__construct($context);
    }

    /**
     * @return $this|ResponseInterface|ResultInterface
     * @throws LocalizedException
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());

        try {
            $collection->walk('delete');

            $this->messageManager->addSuccessMessage(__('Posts has been deleted.'));
        } catch (Exception $e) {
            $this->messageManager->addErrorMessage(__('Something wrong when delete Posts.'));
        }

        /** @var Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);

        return $resultRedirect->setPath('*/*/');
    }
}

Add file at Controller/Adminhtml/Post/MassStatus.php

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Post;

use Exception;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Backend\Model\View\Result\Redirect;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Ui\Component\MassAction\Filter;
use Magelearn\ProductsGrid\Model\ResourceModel\Post\CollectionFactory;

/**
 * Class MassStatus
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Post
 */
class MassStatus extends Action
{
    /**
     * Mass Action Filter
     *
     * @var Filter
     */
    public $filter;

    /**
     * Collection Factory
     *
     * @var CollectionFactory
     */
    public $collectionFactory;

    /**
     * constructor
     *
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     * @param Context $context
     */
    public function __construct(
        Context $context,
        Filter $filter,
        CollectionFactory $collectionFactory
    ) {
        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;

        parent::__construct($context);
    }

    /**
     * @return $this|ResponseInterface|ResultInterface
     * @throws LocalizedException
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());
        $status = (int)$this->getRequest()->getParam('status');

        $postUpdated = 0;
        foreach ($collection as $post) {
            try {
                $post->setEnabled($status)
                    ->save();

                $postUpdated++;
            } catch (LocalizedException $e) {
                $this->messageManager->addErrorMessage($e->getMessage());
            } catch (Exception $e) {
                $this->_getSession()->addException(
                    $e,
                    __('Something went wrong while updating status for %1.', $post->getName())
                );
            }
        }

        if ($postUpdated) {
            $this->messageManager->addSuccessMessage(__('A total of %1 record(s) have been updated.', $postUpdated));
        }

        /** @var Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);

        return $resultRedirect->setPath('*/*/');
    }
}

Add file at Controller/Adminhtml/Post/InlineEdit.php

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Post;

use Exception;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\Result\Json;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\LocalizedException;
use Magelearn\ProductsGrid\Model\Post;
use Magelearn\ProductsGrid\Model\PostFactory;
use RuntimeException;

/**
 * Class InlineEdit
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Post
 */
class InlineEdit extends Action
{
    /**
     * JSON Factory
     *
     * @var JsonFactory
     */
    public $jsonFactory;

    /**
     * Post Factory
     *
     * @var PostFactory
     */
    public $postFactory;

    /**
     * InlineEdit constructor.
     *
     * @param Context $context
     * @param JsonFactory $jsonFactory
     * @param PostFactory $postFactory
     */
    public function __construct(
        Context $context,
        JsonFactory $jsonFactory,
        PostFactory $postFactory
    ) {
        $this->jsonFactory = $jsonFactory;
        $this->postFactory = $postFactory;

        parent::__construct($context);
    }

    /**
     * @return ResultInterface
     */
    public function execute()
    {
        /** @var Json $resultJson */
        $resultJson = $this->jsonFactory->create();
        $error = false;
        $messages = [];
        $postItems = $this->getRequest()->getParam('items', []);
        if (!($this->getRequest()->getParam('isAjax') && !empty($postItems))) {
            return $resultJson->setData([
                'messages' => [__('Please correct the data sent.')],
                'error' => true,
            ]);
        }

        $key = array_keys($postItems);
        $postId = !empty($key) ? (int)$key[0] : '';
        /** @var Post $post */
        $post = $this->postFactory->create()->load($postId);
        try {
            $postData = $postItems[$postId];
            $post->addData($postData);
            $post->save();
        } catch (LocalizedException $e) {
            $messages[] = $this->getErrorWithPostId($post, $e->getMessage());
            $error = true;
        } catch (RuntimeException $e) {
            $messages[] = $this->getErrorWithPostId($post, $e->getMessage());
            $error = true;
        } catch (Exception $e) {
            $messages[] = $this->getErrorWithPostId(
                $post,
                __('Something went wrong while saving the Post.')
            );
            $error = true;
        }

        return $resultJson->setData([
            'messages' => $messages,
            'error' => $error
        ]);
    }

    /**
     * Add Post id to error message
     *
     * @param Post $post
     * @param string $errorText
     *
     * @return string
     */
    public function getErrorWithPostId(Post $post, $errorText)
    {
        return '[Post ID: ' . $post->getId() . '] ' . $errorText;
    }
}

As per the highlighted code in the magelearn_item_post_listing.xml file,

Add file at Model/Config/Source/PostStatus.php to manage post status.

<?php

namespace Magelearn\ProductsGrid\Model\Config\Source;

use Magento\Framework\Option\ArrayInterface;

/**
 * Class PostStatus
 * @package Magelearn\ProductsGrid\Model\Config\Source
 */
class PostStatus implements ArrayInterface
{

    const PENDING = '0';
    const APPROVED = '1';
    const DISAPPROVED = '2';

    /**
     * @return array
     */
    public function toOptionArray()
    {
        $options = [];
        foreach ($this->toArray() as $value => $label) {
            $options[] = [
                'value' => $value,
                'label' => $label
            ];
        }

        return $options;
    }

    /**
     * Get options in "key-value" format
     *
     * @return array
     */
    public function toArray()
    {
        return [
            self::PENDING => __('Pending'),
            self::APPROVED => __('Approved'),
            self::DISAPPROVED => __('Disapproved')
        ];
    }
}

Add file at Ui/Component/Listing/Column/PostActions.php to manage actions for post.

<?php

namespace Magelearn\ProductsGrid\Ui\Component\Listing\Column;

use Magento\Framework\UrlInterface;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Ui\Component\Listing\Columns\Column;

/**
 * Class Actions
 * @package Magelearn\ProductsGrid\Ui\Component\Listing\Columns
 */
class PostActions extends Column
{
    const URL_PATH_EDIT = 'magelearn_productsgrid/post/edit';
    const URL_PATH_DELETE = 'magelearn_productsgrid/post/delete';

    /**
     * @var UrlInterface
     */
    protected $urlBuilder;

    /**
     * Constructor
     *
     * @param ContextInterface $context
     * @param UiComponentFactory $uiComponentFactory
     * @param UrlInterface $urlBuilder
     * @param array $components
     * @param array $data
     */
    public function __construct(
        ContextInterface $context,
        UiComponentFactory $uiComponentFactory,
        UrlInterface $urlBuilder,
        array $components = [],
        array $data = []
    ) {
        $this->urlBuilder = $urlBuilder;

        parent::__construct($context, $uiComponentFactory, $components, $data);
    }

    /**
     * Prepare Data Source
     *
     * @param array $dataSource
     *
     * @return array
     */
    public function prepareDataSource(array $dataSource)
    {
        if (isset($dataSource['data']['items'])) {
            foreach ($dataSource['data']['items'] as &$item) {
                $item[$this->getData('name')] = [
                    'edit' => [
                        'href' => $this->urlBuilder->getUrl(
                            static::URL_PATH_EDIT,
                            [
                                'id' => $item['item_id']
                            ]
                            ),
                        'label' => __('Edit')
                    ],
                    'delete' => [
                        'href' => $this->urlBuilder->getUrl(
                            static::URL_PATH_DELETE,
                            [
                                'id' => $item['item_id']
                            ]
                            ),
                        'label' => __('Delete'),
                        'confirm' => [
                            'title' => __('Delete %1',$item['item_id']),
                            'message' => __('Are you sure you wan\'t to delete a %1 record ?',$item['item_id'])
                        ]
                    ]
                ];
            }
        }

        return $dataSource;
    }
}

Add file Controller/Adminhtml/Post/Delete.php file to manage the delete action.

<?php
declare(strict_types=1);
 
namespace Magelearn\ProductsGrid\Controller\Adminhtml\Post;

use Magento\Backend\App\Action\Context;
use Magento\Framework\Registry;
use Magento\Framework\View\Result\Page;
use Magento\Framework\View\Result\PageFactory;
use Magelearn\ProductsGrid\Controller\Adminhtml\Post;
use Magelearn\ProductsGrid\Model\PostFactory;
 
class Delete extends Post
{
    /**
     * Post Factory
     *
     * @var PostFactory
     */
    public $postFactory;
    
    /**
     * Page factory
     *
     * @var PageFactory
     */
    public $resultPageFactory;
    
    /**
     * Delete constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param PostFactory $postFactory
     * @param PageFactory $resultPageFactory
     */
    public function __construct(
        Context $context,
        Registry $coreRegistry,
        PostFactory $postFactory,
        PageFactory $resultPageFactory
        ) {
            $this->resultPageFactory = $resultPageFactory;
            
            parent::__construct($postFactory, $context, $coreRegistry);
    }
 
    /**
     * Delete action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultRedirectFactory->create();
        // check if we know what should be deleted
        $id = $this->getRequest()->getParam('id');
        if ($id) {
            try {
                // init model and delete
                $model = $this->initPost();
                $model->delete();
                // display success message
                $this->messageManager->addSuccessMessage(__('You deleted the Post.'));
                // go to grid
                return $resultRedirect->setPath('*/*/');
            } catch (\Exception $e) {
                // display error message
                $this->messageManager->addErrorMessage($e->getMessage());
                // go back to edit form
                return $resultRedirect->setPath('*/*/edit', ['id' => $id]);
            }
        }
        // display error message
        $this->messageManager->addErrorMessage(__('We can\'t find a Post to delete.'));
        // go to grid
        return $resultRedirect->setPath('*/*/');
    }
}

As per the highlighted code above, add file at Controller/Adminhtml/Post.php

<?php
declare(strict_types=1);

namespace Magelearn\ProductsGrid\Controller\Adminhtml;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Registry;
use Magelearn\ProductsGrid\Model\PostFactory;

abstract class Post extends Action
{
    
    const ADMIN_RESOURCE = 'Magelearn_ProductsGrid::item';
    
    /**
     * Post Factory
     *
     * @var PostFactory
     */
    public $postFactory;
    
    /**
     * Core registry
     *
     * @var Registry
     */
    public $coreRegistry;
    
    /**
     * @param PostFactory $postFactory
     * @param \Magento\Framework\Registry $coreRegistry
     * @param \Magento\Backend\App\Action\Context $context
     */
    public function __construct(
        PostFactory $postFactory,
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\Registry $coreRegistry
        ) {
            $this->postFactory = $postFactory;
            $this->coreRegistry = $coreRegistry;
            parent::__construct($context);
    }

    /**
     * @param bool $register
     * @param bool $isSave
     *
     * @return bool|\Magelearn\ProductsGrid\Model\Post
     */
    protected function initPost($register = false, $isSave = false)
    {
        $postId = (int)$this->getRequest()->getParam('id');
        
        /** @var \Magelearn\ProductsGrid\Model\Post $post */
        $post = $this->postFactory->create();
        if ($postId) {
            if (!$isSave) {
                $post->load($postId);
                if (!$post->getId()) {
                    $this->messageManager->addErrorMessage(__('This post no longer exists.'));
                    
                    return false;
                }
            }
        }
        
        if ($register) {
            $this->coreRegistry->register('magelearn_item_post', $post);
        }
        
        return $post;
    }
}

To add a new post, add a file at Controller/Adminhtml/Post/NewAction.php

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Post;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Backend\Model\View\Result\Forward;
use Magento\Backend\Model\View\Result\ForwardFactory;

/**
 * Class NewAction
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Post
 */
class NewAction extends Action
{
    /**
     * Redirect result factory
     *
     * @var ForwardFactory
     */
    protected $resultForwardFactory;

    /**
     * constructor
     *
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory
     */
    public function __construct(
        Context $context,
        ForwardFactory $resultForwardFactory
    ) {
        $this->resultForwardFactory = $resultForwardFactory;

        parent::__construct($context);
    }

    /**
     * forward to edit
     * New action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        $resultForward = $this->resultForwardFactory->create();
        $resultForward->forward('edit');

        return $resultForward;
    }
}

This new action will redirect to the edit action. 

For that add a file at Controller/Adminhtml/Post/Edit.php

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Post;

use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\Result\Redirect;
use Magento\Framework\Registry;
use Magento\Framework\View\Result\Page;
use Magento\Framework\View\Result\PageFactory;
use Magelearn\ProductsGrid\Controller\Adminhtml\Post;
use Magelearn\ProductsGrid\Model\PostFactory;

/**
 * Class Edit
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Post
 */
class Edit extends Post
{
    /**
     * Post Factory
     *
     * @var PostFactory
     */
    public $postFactory;
    
    /**
     * Page factory
     *
     * @var PageFactory
     */
    public $resultPageFactory;

    /**
     * Edit constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param PostFactory $postFactory
     * @param PageFactory $resultPageFactory
     */
    public function __construct(
        Context $context,
        Registry $coreRegistry,
        PostFactory $postFactory,
        PageFactory $resultPageFactory
    ) {
        $this->resultPageFactory = $resultPageFactory;

        parent::__construct($postFactory, $context, $coreRegistry);
    }

    /**
     * @return \Magento\Backend\Model\View\Result\Page|Redirect|Page
     */
    public function execute()
    {
        /** @var \Magelearn\ProductsGrid\Model\Post $post */
        // 1. Get ID and create model
        $post = $this->initPost();
        // 2. Initial checking
        if (!$post) {
            $this->messageManager->addErrorMessage(__('This item no longer exists.'));
            /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
            $resultRedirect = $this->resultRedirectFactory->create();
            return $resultRedirect->setPath('*');
        }

        $data = $this->_session->getData('magelearn_item_post_data', true);
        if (!empty($data)) {
            $post->setData($data);
        }

        $this->coreRegistry->register('magelearn_item_post', $post);

        /** @var \Magento\Backend\Model\View\Result\Page|Page $resultPage */
        $resultPage = $this->resultPageFactory->create();
        $resultPage->setActiveMenu('Magelearn_ProductsGrid::post');
        $resultPage->getConfig()->getTitle()->set(__('Posts'));

        $title = $post->getId() ? $post->getName() : __('New Post');
        $resultPage->getConfig()->getTitle()->prepend($title);

        return $resultPage;
    }
}

Add layout file at view/adminhtml/layout/magelearn_productsgrid_post_edit.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head>
        <css src="Magento_Swatches::css/swatches.css"/>
        <css src="jquery/colorpicker/css/colorpicker.css"/>
        <script src="Magelearn_ProductsGrid::js/jscolor.min.js"/>
    </head>
    <update handle="editor"/>
    <body>
        <referenceContainer name="content">
            <block class="Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit" name="magelearn_productsgrid_post_edit"/>
        </referenceContainer>
        <referenceContainer name="left">
            <block class="Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit\Tabs" name="magelearn_productsgrid_post_tabs">
                <container name="main" label="Main">
                    <block class="Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit\Tab\Post" name="magelearn_productsgrid_post_edit_tab_post"/>
                    <block class="Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit\Options\Swatch" as="magelearn_productsgrid_swatch" />
                </container>
                <block class="Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit\Tab\Product" name="magelearn_productsgrid_post_edit_tab_product"/>
            </block>
        </referenceContainer>
    </body>
</page>

As per the highlighted code above we will add view/adminhtml/web/js/jscolor.min.js

Now As per the highlighted code above, we will add Block/Adminhtml/Post/Edit.php file.

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Post;

use Magento\Backend\Block\Widget\Context;
use Magento\Backend\Block\Widget\Form\Container;
use Magento\Framework\Registry;
use Magelearn\ProductsGrid\Model\Post;

/**
 * Class Edit
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Post
 */
class Edit extends Container
{
    /**
     * Core registry
     *
     * @var Registry
     */
    public $coreRegistry;

    /**
     * constructor
     *
     * @param Registry $coreRegistry
     * @param Context $context
     * @param array $data
     */
    public function __construct(
        Registry $coreRegistry,
        Context $context,
        array $data = []
    ) {
        $this->coreRegistry = $coreRegistry;

        parent::__construct($context, $data);
    }

    /**
     * Initialize Post edit block
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_objectId = 'id';
        $this->_blockGroup = 'Magelearn_ProductsGrid';
        $this->_controller = 'adminhtml_post';

        parent::_construct();
        
        $post = $this->coreRegistry->registry('magelearn_item_post');
        //$this->buttonList->remove('reset');
        $this->buttonList->update('save', 'label', __('Save Post'));
        $this->buttonList->add(
            'save-and-continue',
            [
                'label' => __('Save and Continue Edit'),
                'class' => 'save',
                'data_attribute' => [
                    'mage-init' => [
                        'button' => [
                            'event' => 'saveAndContinueEdit',
                            'target' => '#edit_form'
                        ]
                    ]
                ]
            ],
            -100
        );
        
        if ($post->getId() && $this->_request->getParam('id')) {
            $this->buttonList->update('delete', 'label', __('Delete Post'));
        }
        
    }

    /**
     * Retrieve text for header element depending on loaded Post
     *
     * @return string
     */
    public function getHeaderText()
    {
        /** @var Post $post */
        $post = $this->coreRegistry->registry('magelearn_item_post');

        return __('New Post');
    }

    /**
     * Get form action URL
     *
     * @return string
     */
    public function getFormActionUrl()
    {
        /** @var Post $post */
        $post = $this->coreRegistry->registry('magelearn_item_post');
        if ($post->getId()) {
            $ar = ['id' => $post->getId()];

            return $this->getUrl('*/*/save', $ar);
        }

        return parent::getFormActionUrl();
    }
}

Add file at Block/Adminhtml/Post/Edit/Form.php

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit;

use Magento\Backend\Block\Widget\Form\Generic;

/**
 * Class Form
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit
 */
class Form extends Generic
{
    /**
     * @inheritdoc
     */
    protected function _prepareForm()
    {
        /** @var \Magento\Framework\Data\Form $form */
        $form = $this->_formFactory->create([
            'data' => [
                'id' => 'edit_form',
                'action' => $this->getData('action'),
                'method' => 'post',
                'enctype' => 'multipart/form-data'
            ]
        ]);
        $form->setUseContainer(true);
        $this->setForm($form);

        return parent::_prepareForm();
    }
}

Add file at Block/Adminhtml/Post/Edit/Tabs.php

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit;

/**
 * Class Tabs
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit
 */
class Tabs extends \Magento\Backend\Block\Widget\Tabs
{
    /**
     * constructor
     *
     * @return void
     */
    protected function _construct()
    {
        parent::_construct();

        $this->setId('post_tabs');
        $this->setDestElementId('edit_form');
        $this->setTitle(__('Post Information'));
    }
    
    /**
     * @return $this
     */
    protected function _beforeToHtml()
    {
        $this->addTab(
            'main',
            [
                'label' => __('Post'),
                'title' => __('Post'),
                'content' => $this->getChildHtml('main'),
                'active' => true
            ]
        );
        $this->addTab(
            'product',
            'magelearn_productsgrid_post_edit_tab_product'
        );
            
        return parent::_beforeToHtml();
    }
}

Now add Block/Adminhtml/Post/Edit/Tab/Post.php file.

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit\Tab;

use DateTimeZone;
use Exception;
use Magento\Backend\Block\Store\Switcher\Form\Renderer\Fieldset\Element;
use Magento\Backend\Block\Template\Context;
use Magento\Backend\Block\Widget\Form\Generic;
use Magento\Backend\Block\Widget\Tab\TabInterface;
use Magento\Cms\Model\Wysiwyg\Config;
use Magento\Config\Model\Config\Source\Yesno;
use Magento\Framework\Data\Form;
use Magento\Framework\Data\Form\Element\Renderer\RendererInterface;
use Magento\Framework\Data\FormFactory;
use Magento\Framework\Registry;
use Magento\Framework\Stdlib\DateTime\DateTime;
use Magento\Store\Model\System\Store;
use Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit\Tab\Renderer\Category;
use Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit\Tab\Renderer\Tag;
use Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit\Options\Swatch;
use Magelearn\ProductsGrid\Helper\Image;
use Magelearn\ProductsGrid\Model\Config\Source\PostStatus;

/**
 * Class Post
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit\Tab
 */
class Post extends Generic implements TabInterface
{
    /**
     * Wysiwyg config
     *
     * @var Config
     */
    public $wysiwygConfig;

    /**
     * Country options
     *
     * @var Yesno
     */
    public $booleanOptions;

    /**
     * @var Store
     */
    public $systemStore;

    /**
     * @var Image
     */
    protected $imageHelper;
    
    /**
     * @var PostStatus
     */
    protected $_status;

    /**
     * @var DateTime
     */
    protected $_date;

    /**
     * Post constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param DateTime $dateTime
     * @param FormFactory $formFactory
     * @param Config $wysiwygConfig
     * @param Yesno $booleanOptions
     * @param Store $systemStore
     * @param Image $imageHelper
     * @param PostStatus $status
     * @param array $data
     */
    public function __construct(
        Context $context,
        Registry $registry,
        DateTime $dateTime,
        FormFactory $formFactory,
        Config $wysiwygConfig,
        Yesno $booleanOptions,
        Store $systemStore,
        Image $imageHelper,
        PostStatus $status,
        array $data = []
    ) {
        $this->wysiwygConfig = $wysiwygConfig;
        $this->booleanOptions = $booleanOptions;
        $this->systemStore = $systemStore;
        $this->_date = $dateTime;
        $this->imageHelper = $imageHelper;
        $this->_status = $status;

        parent::__construct($context, $registry, $formFactory, $data);
    }

    /**
     * @inheritdoc
     * @throws Exception
     */
    protected function _prepareForm()
    {
        /** @var \Magelearn\ProductsGrid\Model\Post $post */
        $post = $this->_coreRegistry->registry('magelearn_item_post');

        /** @var Form $form */
        $form = $this->_formFactory->create();

        $form->setHtmlIdPrefix('post_');
        $form->setFieldNameSuffix('post');
        
        if ($post->getId()) {
            $fieldset = $form->addFieldset(
                'base_fieldset',
                ['legend' => __('Post Information'), 'class' => 'fieldset-wide']
                );
            $fieldset->addField('item_id', 'hidden', ['name' => 'item_id']);
        } else {
            $fieldset = $form->addFieldset(
                'base_fieldset',
                ['legend' => __('Add New Post'), 'class' => 'fieldset-wide']
                );
        }

        $fieldset->addField('name', 'text', [
            'name' => 'name',
            'label' => __('Name'),
            'title' => __('Name'),
            'required' => true
        ]);
        
        $fieldset->addField('enabled', 'select', [
            'name' => 'enabled',
            'label' => __('Status'),
            'title' => __('Status'),
            'values' => $this->_status->toOptionArray()
        ]);
        if (!$post->hasData('enabled')) {
            $post->setEnabled(1);
        }

        $fieldset->addField('short_description', 'textarea', [
            'name' => 'short_description',
            'label' => __('Short Description'),
            'title' => __('Short Description')
        ]);
        $fieldset->addField('item_content', 'editor', [
            'name' => 'item_content',
            'label' => __('Content'),
            'title' => __('Content'),
            'config' => $this->wysiwygConfig->getConfig([
                'add_variables' => false,
                'add_widgets' => true,
                'add_directives' => true
            ])
        ]);

        if ($this->_storeManager->isSingleStoreMode()) {
            $fieldset->addField('store_ids', 'hidden', [
                'name' => 'store_ids',
                'value' => $this->_storeManager->getStore()->getId()
            ]);
        } else {
            /** @var RendererInterface $rendererBlock */
            $rendererBlock = $this->getLayout()->createBlock(Element::class);
            $fieldset->addField('store_ids', 'multiselect', [
                'name' => 'store_ids',
                'label' => __('Store Views'),
                'title' => __('Store Views'),
                'values' => $this->systemStore->getStoreValuesForForm(false, true)
            ])->setRenderer($rendererBlock);

            if (!$post->hasData('store_ids')) {
                $post->setStoreIds(0);
            }
        }

        $fieldset->addField('image', \Magelearn\ProductsGrid\Block\Adminhtml\Renderer\Image::class, [
            'name' => 'image',
            'label' => __('Image'),
            'title' => __('Image'),
            'path' => $this->imageHelper->getBaseMediaPath(Image::TEMPLATE_MEDIA_TYPE_POST),
            'note' => __('The appropriate size is 265px * 250px.')
        ]);

        $fieldset->addField('categories_ids', Category::class, [
            'name' => 'categories_ids',
            'label' => __('Categories'),
            'title' => __('Categories'),
        ]);
        if (!$post->getCategoriesIds()) {
            $post->setCategoriesIds($post->getCategoryIds());
        }

        $fieldset->addField('tags_ids', Tag::class, [
            'name' => 'tags_ids',
            'label' => __('Tags'),
            'title' => __('Tags'),
        ]);
        if (!$post->getTagsIds()) {
            $post->setTagsIds($post->getTagIds());
        }
        
        $fieldset = $form->addFieldset(
            'itemconfiguration_fieldset',
            [
                'legend' => __('Post Configuration'),
                'class' => 'fieldset-wide'
            ]
            );
        
        $fieldset->addField(
            'item_title_color',
            'text',
            [
                'name' => 'item_title_color',
                'label' => __('Choose title color'),
                'title' => __('Choose title color'),
                'class' => 'jscolor {hash:true,refine:false}',
            ]
            );

        $form->addValues($post->getData());
        $this->setForm($form);

        return parent::_prepareForm();
    }

    /**
     * Prepare label for tab
     *
     * @return string
     */
    public function getTabLabel()
    {
        return __('Post');
    }

    /**
     * Prepare title for tab
     *
     * @return string
     */
    public function getTabTitle()
    {
        return $this->getTabLabel();
    }

    /**
     * Can show tab in tabs
     *
     * @return boolean
     */
    public function canShowTab()
    {
        return true;
    }

    /**
     * Tab is hidden
     *
     * @return boolean
     */
    public function isHidden()
    {
        return false;
    }
}

Now as per the highlighted code above we will add different files to render the Tag, Category and Image for the Post edit form.

Add file at Block/Adminhtml/Renderer/Image.php

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Renderer;

/**
 * Renderer image for admin form
 * Add media path to url
 *
 * Class Image
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Renderer
 */
class Image extends \Magento\Framework\Data\Form\Element\Image
{
    /**
     * @return string
     */
    protected function _getDeleteCheckbox()
    {
        $html = '';
        if ($this->getValue()) {
            $label = __('Delete Image');
            $html .= '<span class="delete-image">';
            $html .= '<input style="margin: auto;" type="checkbox"' .
                ' name="' .
                $this->getName() .
                '[delete]" value="1" class="checkbox"' .
                ' id="' .
                $this->getHtmlId() .
                '_delete"' .
                ($this->getDisabled() ? ' disabled="disabled"' : '') .
                '/>';
            $html .= '<label for="' . $this->getHtmlId() .
                '_delete"' . ($this->getDisabled() ? ' class="disabled"' : '') . '> ' .
                $label .
                '</label>';
            $html .= $this->_getHiddenInput();
            $html .= '</span>';
        }
        $html .= '<style>#post_image_image{ position: relative;top: 6px }</style>';

        return $html;
    }
    
    /**
     * Get image preview url
     *
     * @return string
     */
    protected function _getUrl()
    {
        $url = parent::_getUrl();
        
        if ($this->getPath()) {
            $url = $this->getPath() . '/' . trim($url, '/');
        }
        
        return $url;
    }
}

Add file at Block/Adminhtml/Post/Edit/Tab/Renderer/Tag.php

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit\Tab\Renderer;

use Magento\Framework\AuthorizationInterface;
use Magento\Framework\Data\Form\Element\CollectionFactory;
use Magento\Framework\Data\Form\Element\Factory;
use Magento\Framework\Data\Form\Element\Multiselect;
use Magento\Framework\Escaper;
use Magento\Framework\UrlInterface;
use Magelearn\ProductsGrid\Model\ResourceModel\Tag\Collection;
use Magelearn\ProductsGrid\Model\ResourceModel\Tag\CollectionFactory as PostTagCollectionFactory;

/**
 * Class Tag
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit\Tab\Renderer
 */
class Tag extends Multiselect
{
    /**
     * @var PostTagCollectionFactory
     */
    public $collectionFactory;

    /**
     * Authorization
     *
     * @var AuthorizationInterface
     */
    public $authorization;

    /**
     * @var UrlInterface
     */
    protected $_urlBuilder;

    /**
     * Tag constructor.
     *
     * @param Factory $factoryElement
     * @param CollectionFactory $factoryCollection
     * @param Escaper $escaper
     * @param PostTagCollectionFactory $collectionFactory
     * @param AuthorizationInterface $authorization
     * @param UrlInterface $urlBuilder
     * @param array $data
     */
    public function __construct(
        Factory $factoryElement,
        CollectionFactory $factoryCollection,
        Escaper $escaper,
        PostTagCollectionFactory $collectionFactory,
        AuthorizationInterface $authorization,
        UrlInterface $urlBuilder,
        array $data = []
    ) {
        $this->collectionFactory = $collectionFactory;
        $this->authorization = $authorization;
        $this->_urlBuilder = $urlBuilder;

        parent::__construct($factoryElement, $factoryCollection, $escaper, $data);
    }

    /**
     * @inheritdoc
     */
    public function getElementHtml()
    {
        $html = '<div class="admin__field-control admin__control-grouped">';
        $html .= '<div id="post-tag-select" class="admin__field" data-bind="scope:\'postTag\'" data-index="index">';
        $html .= '<!-- ko foreach: elems() -->';
        $html .= '<input name="post[tags_ids]" data-bind="value: value" style="display: none"/>';
        $html .= '<!-- ko template: elementTmpl --><!-- /ko -->';
        $html .= '<!-- /ko -->';
        $html .= '</div>';

        $html .= '<div class="admin__field admin__field-group-additional admin__field-small"'
            . 'data-bind="scope:\'create_tag_button\'">';
        $html .= '<div class="admin__field-control">';
        $html .= '<!-- ko template: elementTmpl --><!-- /ko -->';
        $html .= '</div></div></div>';

        $html .= '<!-- ko scope: \'create_tag_modal\' --><!-- ko template: getTemplate() --><!-- /ko --><!-- /ko -->';

        $html .= $this->getAfterElementHtml();

        return $html;
    }

    /**
     * Get no display
     *
     * @return bool
     */
    public function getNoDisplay()
    {
        $isNotAllowed = !$this->authorization->isAllowed('Magelearn_ProductsGrid::tag');

        return $this->getData('no_display') || $isNotAllowed;
    }

    /**
     * @return mixed
     */
    public function getTagsCollection()
    {
        /* @var $collection Collection */
        $collection = $this->collectionFactory->create();
        $collection->addFieldToFilter('enabled', 1);
        $tagById = [];
        foreach ($collection as $tag) {
            $tagById[$tag->getId()]['value'] = $tag->getId();
            $tagById[$tag->getId()]['is_active'] = 1;
            $tagById[$tag->getId()]['label'] = $tag->getName();
        }

        return $tagById;
    }

    /**
     * Get values for select
     *
     * @return array
     */
    public function getValues()
    {
        $values = $this->getValue();

        if (!is_array($values)) {
            $values = explode(',', $values);
        }

        if (!count($values)) {
            return [];
        }

        /* @var $collection Collection */
        $collection = $this->collectionFactory->create()
            ->addFieldToFilter('enabled', 1)
            ->addIdFilter($values);

        $options = [];
        foreach ($collection as $tag) {
            $options[] = $tag->getId();
        }

        return $options;
    }

    /**
     * Attach Post Tag suggest widget initialization
     *
     * @return string
     */
    public function getAfterElementHtml()
    {
        $html = '<script type="text/x-magento-init">
            {
                "*": {
                    "Magento_Ui/js/core/app": {
                        "components": {
                            "postTag": {
                                "component": "uiComponent",
                                "children": {
                                    "post_select_tag": {
                                        "component": "Magelearn_ProductsGrid/js/components/new-category",
                                        "config": {
                                            "filterOptions": true,
                                            "disableLabel": true,
                                            "chipsEnabled": true,
                                            "levelsVisibility": "1",
                                            "elementTmpl": "ui/grid/filters/elements/ui-select",
                                            "options": ' . json_encode($this->getTagsCollection()) . ',
                                            "value": ' . json_encode($this->getValues()) . ',
                                            "listens": {
                                                "index=create_tag:responseData": "setParsed",
                                                "newOption": "toggleOptionSelected"
                                            },
                                            "config": {
                                                "dataScope": "post_select_tag",
                                                "sortOrder": 10
                                            }
                                        }
                                    }
                                }
                            },
                            "create_tag_button": {
                                "title": "' . __('New Tag') . '",
                                "formElement": "container",
                                "additionalClasses": "admin__field-small",
                                "componentType": "container",
                                "component": "Magento_Ui/js/form/components/button",
                                "template": "ui/form/components/button/container",
                                "actions": [
                                    {
                                        "targetName": "create_tag_modal",
                                        "actionName": "toggleModal"
                                    },
                                    {
                                        "targetName": "create_tag_modal.create_tag",
                                        "actionName": "render"
                                    },
                                    {
                                        "targetName": "create_tag_modal.create_tag",
                                        "actionName": "resetForm"
                                    }
                                ],
                                "additionalForGroup": true,
                                "provider": false,
                                "source": "product_details",
                                "displayArea": "insideGroup"
                            },
                            "create_tag_modal": {
                                "config": {
                                    "isTemplate": false,
                                    "componentType": "container",
                                    "component": "Magento_Ui/js/modal/modal-component",
                                    "options": {
                                        "title": "' . __('New Tag') . '",
                                        "type": "slide"
                                    },
                                    "imports": {
                                        "state": "!index=create_tag:responseStatus"
                                    }
                                },
                                "children": {
                                    "create_tag": {
                                        "label": "",
                                        "componentType": "container",
                                        "component": "Magento_Ui/js/form/components/insert-form",
                                        "dataScope": "",
                                        "update_url": "' . $this->_urlBuilder->getUrl('mui/index/render') . '",
                                        "render_url": "' .
            $this->_urlBuilder->getUrl(
                'mui/index/render_handle',
                [
                    'handle' => 'magelearn_productsgrid_tag_create',
                    'buttons' => 1
                ]
            ) . '",
                                        "autoRender": false,
                                        "ns": "item_new_tag_form",
                                        "externalProvider": "item_new_tag_form.new_tag_form_data_source",
                                        "toolbarContainer": "${ $.parentName }",
                                        "formSubmitType": "ajax"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        </script>';

        return $html;
    }
}

To render the category add a file at Block/Adminhtml/Post/Edit/Tab/Renderer/Category.php

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit\Tab\Renderer;

use Magento\Framework\AuthorizationInterface;
use Magento\Framework\Data\Form\Element\CollectionFactory;
use Magento\Framework\Data\Form\Element\Factory;
use Magento\Framework\Data\Form\Element\Multiselect;
use Magento\Framework\Escaper;
use Magento\Framework\UrlInterface;
use Magelearn\ProductsGrid\Model\ResourceModel\Category\Collection;
use Magelearn\ProductsGrid\Model\ResourceModel\Category\CollectionFactory as PostCategoryCollectionFactory;

/**
 * Class Category
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit\Tab\Renderer
 */
class Category extends Multiselect
{
    /**
     * @var PostCategoryCollectionFactory
     */
    public $collectionFactory;

    /**
     * @var AuthorizationInterface
     */
    public $authorization;

    /**
     * @var UrlInterface
     */
    protected $_urlBuilder;

    /**
     * Category constructor.
     *
     * @param Factory $factoryElement
     * @param CollectionFactory $factoryCollection
     * @param Escaper $escaper
     * @param PostCategoryCollectionFactory $collectionFactory
     * @param AuthorizationInterface $authorization
     * @param UrlInterface $urlBuilder
     * @param array $data
     */
    public function __construct(
        Factory $factoryElement,
        CollectionFactory $factoryCollection,
        Escaper $escaper,
        PostCategoryCollectionFactory $collectionFactory,
        AuthorizationInterface $authorization,
        UrlInterface $urlBuilder,
        array $data = []
    ) {
        $this->collectionFactory = $collectionFactory;
        $this->authorization = $authorization;
        $this->_urlBuilder = $urlBuilder;

        parent::__construct($factoryElement, $factoryCollection, $escaper, $data);
    }

    /**
     * @inheritdoc
     */
    public function getElementHtml()
    {
        $html = '<div class="admin__field-control admin__control-grouped">';
        $html .= '<div id="post-category-select" class="admin__field"
                    data-bind="scope:\'postCategory\'" data-index="index">';
        $html .= '<!-- ko foreach: elems() -->';
        $html .= '<input name="post[categories_ids]" data-bind="value: value" style="display: none"/>';
        $html .= '<!-- ko template: elementTmpl --><!-- /ko -->';
        $html .= '<!-- /ko -->';
        $html .= '</div>';

        $html .= '<div class="admin__field admin__field-group-additional admin__field-small"
                  data-bind="scope:\'create_category_button\'">';
        $html .= '<div class="admin__field-control">';
        $html .= '<!-- ko template: elementTmpl --><!-- /ko -->';
        $html .= '</div></div></div>';

        $html .= '<!-- ko scope: \'create_category_modal\' -->
        <!-- ko template: getTemplate() --><!-- /ko --><!-- /ko -->';

        $html .= $this->getAfterElementHtml();

        return $html;
    }

    /**
     * Get no display
     *
     * @return bool
     */
    public function getNoDisplay()
    {
        $isNotAllowed = !$this->authorization->isAllowed('Magelearn_ProductsGrid::category');

        return $this->getData('no_display') || $isNotAllowed;
    }
    
    /**
     * @return mixed
     */
    public function getCategoriesCollection()
    {
        /* @var $collection Collection */
        $collection = $this->collectionFactory->create();
        $collection->addFieldToFilter('enabled', 1);
        $categoryById = [];
        foreach ($collection as $category) {
            $categoryById[$category->getId()]['value'] = $category->getId();
            $categoryById[$category->getId()]['is_active'] = 1;
            $categoryById[$category->getId()]['label'] = $category->getName();
        }
        
        return $categoryById;
    }

    /**
     * Get values for select
     *
     * @return array
     */
    public function getValues()
    {
        $values = $this->getValue();

        if (!is_array($values)) {
            $values = explode(',', $values);
        }

        if (!count($values)) {
            return [];
        }

        /* @var $collection Collection */
        $collection = $this->collectionFactory->create()
            ->addFieldToFilter('enabled', 1)
            ->addIdFilter($values);

        $options = [];
        foreach ($collection as $category) {
            $options[] = $category->getId();
        }

        return $options;
    }

    /**
     * Attach Post Category suggest widget initialization
     *
     * @return string
     */
    public function getAfterElementHtml()
    {
        $html = '<script type="text/x-magento-init">
            {
                "*": {
                    "Magento_Ui/js/core/app": {
                        "components": {
                            "postCategory": {
                                "component": "uiComponent",
                                "children": {
                                    "post_select_category": {
                                        "component": "Magelearn_ProductsGrid/js/components/new-category",
                                        "config": {
                                            "filterOptions": true,
                                            "disableLabel": true,
                                            "chipsEnabled": true,
                                            "levelsVisibility": "1",
                                            "elementTmpl": "ui/grid/filters/elements/ui-select",
                                            "options": ' . json_encode($this->getCategoriesCollection()) . ',
                                            "value": ' . json_encode($this->getValues()) . ',
                                            "listens": {
                                                "index=create_category:responseData": "setParsed",
                                                "newOption": "toggleOptionSelected"
                                            },
                                            "config": {
                                                "dataScope": "post_select_category",
                                                "sortOrder": 10
                                            }
                                        }
                                    }
                                }
                            },
                            "create_category_button": {
                                "title": "' . __('New Category') . '",
                                "formElement": "container",
                                "additionalClasses": "admin__field-small",
                                "componentType": "container",
                                "component": "Magento_Ui/js/form/components/button",
                                "template": "ui/form/components/button/container",
                                "actions": [
                                    {
                                        "targetName": "create_category_modal",
                                        "actionName": "toggleModal"
                                    },
                                    {
                                        "targetName": "create_category_modal.create_category",
                                        "actionName": "render"
                                    },
                                    {
                                        "targetName": "create_category_modal.create_category",
                                        "actionName": "resetForm"
                                    }
                                ],
                                "additionalForGroup": true,
                                "provider": false,
                                "source": "product_details",
                                "displayArea": "insideGroup"
                            },
                            "create_category_modal": {
                                "config": {
                                    "isTemplate": false,
                                    "componentType": "container",
                                    "component": "Magento_Ui/js/modal/modal-component",
                                    "options": {
                                        "title": "' . __('New Category') . '",
                                        "type": "slide"
                                    },
                                    "imports": {
                                        "state": "!index=create_category:responseStatus"
                                    }
                                },
                                "children": {
                                    "create_category": {
                                        "label": "",
                                        "componentType": "container",
                                        "component": "Magento_Ui/js/form/components/insert-form",
                                        "dataScope": "",
                                        "update_url": "' . $this->_urlBuilder->getUrl('mui/index/render') . '",
                                        "render_url": "' .
            $this->_urlBuilder->getUrl(
                'mui/index/render_handle',
                ['handle' => 'magelearn_productsgrid_category_create', 'buttons' => 1]
            ) . '",
                                        "autoRender": false,
                                        "ns": "item_new_category_form",
                                        "externalProvider": "item_new_category_form.new_category_form_data_source",
                                        "toolbarContainer": "${ $.parentName }",
                                        "formSubmitType": "ajax"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        </script>';

        return $html;
    }
}

Now as per the Highlighted code in magelearn_productsgrid_post_edit.xml file, we will also add Block file to display Add/Edit Swatches for the post.

Add file at Block/Adminhtml/Post/Edit/Options/Swatch.php

<?php
namespace Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit\Options;

use Magento\Backend\Block\Template\Context;
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\CollectionFactory;
use Magento\Framework\Registry;
use Magento\Framework\Validator\UniversalFactory;
use Magelearn\ProductsGrid\Helper\SwatchMedia;
use Magelearn\ProductsGrid\Helper\Data;
use Magelearn\ProductsGrid\Model\Post;
use Magelearn\ProductsGrid\Model\Swatch as SwatchModel;
use Magelearn\ProductsGrid\Model\SwatchRepository;
use Magelearn\ProductsGrid\Model\ResourceModel\Swatch\Collection as SwatchCollection;

class Swatch extends \Magento\Eav\Block\Adminhtml\Attribute\Edit\Options\Options
{
    /**
     * {@inheritdoc}
     */
    protected $_template = 'Magelearn_ProductsGrid::post/option/swatch.phtml';
    
    /**
     * @var SwatchMedia
     */
    private $swatchMediaHelper;
    
    /**
     * @var SwatchRepository
     */
    private $swatchRepository;
    
    /**
     * Swatch constructor.
     * @param SwatchMedia $swatchMediaHelper
     * @param SwatchRepository $swatchRepository
     * @param Context $context
     * @param Registry $registry
     * @param CollectionFactory $attrOptionCollectionFactory
     * @param UniversalFactory $universalFactory
     * @param array $data
     */
    public function __construct(
        SwatchMedia $swatchMediaHelper,
        SwatchRepository $swatchRepository,
        Context $context,
        Registry $registry,
        CollectionFactory $attrOptionCollectionFactory,
        UniversalFactory $universalFactory,
        array $data = []
        ) {
            parent::__construct($context, $registry, $attrOptionCollectionFactory, $universalFactory, $data);
            $this->swatchMediaHelper = $swatchMediaHelper;
            $this->swatchRepository = $swatchRepository;
    }
    
    /**
     * @return array
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function getValues()
    {
        $values = [];
        /** @var Post $post */
        $post = $this->_registry->registry('magelearn_item_post');
        if (!$post->getId()) {
            return $values;
        }
        $list = $this->swatchRepository->getListByPostId($post->getId());
        $swatchData = $list->getData();

        foreach ($swatchData as $swatch) {
            /** @var SwatchModel $swatch */
            $value = [
                'id' => $swatch['swatch_id'],
                'checked' => '',
                'intype' => 'radio',
                'description' => $swatch['description'],
                'sort_order' => $swatch['position'],
                'thumbnail' => null,
                'image' => null,
                'defaultswatch0' => ''
            ];
            $swatchImage = $swatch['image'];
            if (null !== $swatchImage && !empty($swatchImage)) {
                $value['thumbnail'] = $swatchImage;
                $value['image'] = $this->getImageStyle($swatchImage);
                unset($value['defaultswatch0']);
            }
            $values[] = $value;
        }
        return $values;
    }
    
    /**
     * @param string $image
     * @return string
     */
    protected function getImageStyle(string $image = '')
    {
        if (!empty($image)) {
            if ($image[0] == '#') {
                return 'background: '.$image;
            } elseif ($image[0] == '/') {
                $path = $this->swatchMediaHelper->getSwatchAttributeImage('swatch_image', $image);
                return 'background: url('. $path .'); background-size: cover;';
            }
        }
        return $image;
    }
    
    /**
     * @return false|string
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function getJsConfig()
    {
        $data = [
            'attributesData' => $this->getValues(),
            'uploadActionUrl' => $this->getUrl('magelearn_productsgrid/swatch/show'),
            'isSortable' => 'true',
            'isReadOnly' => 'false'
        ];
        return json_encode($data);
    }
    
}

As per the Highlighted code above add the file at view/adminhtml/templates/post/option/swatch.phtml

<?php
/** @var \Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit\Options\Swatch $block */
?>
<fieldset class="fieldset">
    <legend class="legend">
        <span><?php echo $block->escapeHtml(__('Manage Swatch'))?></span>
    </legend>
    <div id="swatch-visual-options-panel">
        <table class="data-table clearfix" data-index="attribute_options_select">
            <thead>
                <tr id="swatch-visual-options-table">
                    <th class="col-draggable"></th>
                    <th><span><?php echo $block->escapeHtml(__('Swatch Image')) ?></span></th>
                    <th class="col-default _required">
                        <span><?php echo $block->escapeHtml(__('Swatch Content')) ?></span>
                    </th>
                    <th class="col-delete">&nbsp;</th>
                </tr>
            </thead>
            <tbody data-role="swatch-visual-options-container" class="ignore-validate"></tbody>
            <tfoot>
            <tr>
                <th colspan="3" class="validation">
                    <input type="hidden" class="required-dropdown-attribute-entry"
                           name="dropdown_attribute_validation"/>
                </th>
            </tr>
            <tr>
                <th colspan="3" class="col-actions-add">
                    <button id="add_new_swatch_visual_option_button" data-action="add_new_row"
                            title="<?php echo $block->escapeHtml(__('Add Swatch')); ?>"
                            type="button" class="action- scalable add">
                        <span><?php echo $block->escapeHtml(__('Add Swatch')); ?></span>
                    </button>
                </th>
            </tr>
            </tfoot>
        </table>
        <input type="hidden" id="swatch-visual-option-count-check" value="" />
    </div>
    <script id="swatch-visual-row-template" type="text/x-magento-template">
        <tr>
            <td class="col-draggable">
                <div data-role="draggable-handle" class="draggable-handle"
                     title="<?php echo $block->escapeHtml(__('Sort Option')); ?>"></div>
                <input
                        data-role="order"
                        type="hidden"
                        name="option[<%- data.id %>][order]"
                        value="<%- data.sort_order %>"/>
            </td>
            <td class="swatches-visual-col col-default <%- data.empty_class %>">
                <input id="swatch_visual_value_<%- data.id %>" type="hidden"
                       name="option[<%- data.id %>][image]" value="<%- data.thumbnail %>" />
                <div class="swatch_window" id="swatch_window_option_<%- data.id %>" style="<%- data.image %>"></div>
                <div class="swatch_sub-menu_container" id="swatch_container_option_<%- data.id %>">
                    <div class="swatch_row position-relative">
                        <div class="swatch_row_name colorpicker_handler">
                            <p><?php echo $block->escapeHtml(__('Choose a color')); ?></p>
                        </div>
                    </div>
                    <div class="swatch_row">
                        <div class="swatch_row_name btn_choose_file_upload"
                             id="swatch_choose_file_option_<%- data.id %>">
                            <p><?php echo $block->escapeHtml(__('Upload a file')); ?></p>
                        </div>
                    </div>
                    <div class="swatch_row">
                        <div class="swatch_row_name btn_remove_swatch">
                            <p><?php echo $block->escapeHtml(__('Clear')); ?></p>
                        </div>
                    </div>
                </div>
            </td>
            <td class="col-<%- data.id %>">
                <input
                        name="option[<%- data.id %>][description]"
                        value="<%- data.description %>"
                        class="input-text required-option"
                        type="text"
                />
            </td>
            <td id="delete_button_swatch_container_<%- data.id %>" class="col-delete">
                <input type="hidden" class="delete-flag" name="option[<%- data.id %>][delete]" value="" />
                <button
                        id="delete_button_<%- data.id %>"
                        title="<?php echo $block->escapeHtml(__('Delete')) ?>"
                        type="button"
                        class="action- scalable delete delete-option">
                    <span><?php echo $block->escapeHtml(__('Delete')) ?></span>
                </button>
            </td>
        </tr>
    </script>
    <script type="text/x-magento-init">
    {
        "*": {
            "Magelearn_ProductsGrid/js/swatch": <?php /* @noEscape */ echo $block->getJsConfig(); ?>
        }
    }
    </script>
</fieldset>

As highlighted above add a file at view/adminhtml/web/js/swatch.js

define(
[
    'Magento_Swatches/js/visual',
    'jquery',
    'uiRegistry'
], function (Component, jQuery, registry) {
        'use strict';
        return function (config) {
            Component(config);
            var attributeOptions = registry.get('swatch-visual-options-panel');
            if (typeof attributeOptions === 'object') {
                jQuery('#swatch-visual-options-panel').trigger('render');
            }
        }
    }
);

Add file at Controller/Adminhtml/Swatch/Show.php

<?php
namespace Magelearn\ProductsGrid\Controller\Adminhtml\Swatch;

class Show extends \Magento\Swatches\Controller\Adminhtml\Iframe\Show
{
    const ADMIN_RESOURCE = 'Magento_Backend::admin';
}

Add file at Helper/SwatchMedia.php

<?php
namespace Magelearn\ProductsGrid\Helper;

class SwatchMedia extends \Magento\Swatches\Helper\Media
{
    const SWATCH_MEDIA_PATH = 'attribute/swatch';
    const SWATCH_MAIN_IMAGE = 'swatch_image';

    /**
     * @var array
     */
    protected $swatchImageTypes = [self::SWATCH_MAIN_IMAGE, 'swatch_thumb'];

    /**
     * Media swatch path
     *
     * @return string
     */
    public function getSwatchMediaPath()
    {
        return static::SWATCH_MEDIA_PATH;
    }

    /**
     * Media path with swatch_image or swatch_thumb folder
     *
     * @param string $swatchType
     * @return string
     */
    public function getSwatchCachePath($swatchType)
    {
        return $this->getSwatchMediaPath() . '/' . $swatchType . '/';
    }
}

Now as per the highlighted code in Block/Adminhtml/Post/Edit/Tab/Renderer/Tag.php file we will add some necessary files.

Add JS component file at view/adminhtml/web/js/components/new-category.js

define([
    'underscore',
    'Magento_Catalog/js/components/new-category'
], function (_, Category) {
    'use strict';

    /**
     * Processing options list
     *
     * @param {Array} array - Property array
     * @param {String} separator - Level separator
     * @param {Array} created - list to add new options
     *
     * @return {Array} Plain options list
     */
    function flattenCollection(array, separator, created) {
        var i = 0,
            length,
            childCollection;

        array = _.compact(array);
        length = array.length;
        created = created || [];

        for (i; i < length; i++) {
            created.push(array[i]);

            if (array[i].hasOwnProperty(separator)) {
                childCollection = array[i][separator];
                delete array[i][separator];
                flattenCollection.call(this, childCollection, separator, created);
            }
        }

        return created;
    }

    return Category.extend({
        /**
         * Get path to current option
         *
         * @param {Object} data - option data
         * @returns {String} path
         */
        getPath: function (data) {
            var pathParts,
                createdPath = '';

            if (this.renderPath && typeof data.path !== "undefined") {
                pathParts = data.path.split('.');
                _.each(pathParts, function (curData) {
                    createdPath = createdPath ? createdPath + ' / ' + curData : curData;
                });

                return createdPath;
            }
        },

        /**
         * Set option to options array.
         *
         * @param {Object} option
         * @param {Array} options
         */
        setOption: function (option, options) {
            // eslint-disable-next-line radix
            var parent = parseInt(option.parent),
                copyOptionsTree;

            if (_.contains([0, 1], parent)) {
                options = options || this.cacheOptions.tree;
                options.push(option);

                copyOptionsTree = JSON.parse(JSON.stringify(this.cacheOptions.tree));
                this.cacheOptions.plain = flattenCollection(copyOptionsTree, this.separator);
                this.options(this.cacheOptions.tree);
            } else {
                this._super(option, options);
            }
        }
    });
});

Add file at view/adminhtml/layout/magelearn_productsgrid_tag_create.xml file.

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="styles"/>
    <body>
        <referenceContainer name="content">
            <uiComponent name="item_new_tag_form"/>
        </referenceContainer>
    </body>
</page>

Add ui_component file at view/adminhtml/ui_component/item_new_tag_form.xml

<?xml version="1.0" encoding="UTF-8"?>
<form 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">item_new_tag_form.new_tag_form_data_source</item>
            <item name="deps" xsi:type="string">item_new_tag_form.new_tag_form_data_source</item>
            <item name="namespace" xsi:type="string">item_new_tag_form</item>
        </item>
        <item name="buttons" xsi:type="array">
            <item name="save" xsi:type="string">Magelearn\ProductsGrid\Block\Adminhtml\Tag\Edit\Button\CreateTag</item>
        </item>
        <item name="template" xsi:type="string">templates/form/collapsible</item>
        <item name="config" xsi:type="array">
            <item name="ajaxSaveType" xsi:type="string">simple</item>
        </item>
    </argument>
    <dataSource name="new_tag_form_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Magelearn\ProductsGrid\Ui\DataProvider\Item\Form\NewTagDataProvider</argument>
            <argument name="name" xsi:type="string">new_tag_form_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">entity_id</argument>
            <argument name="requestFieldName" xsi:type="string">id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="submit_url" xsi:type="url" path="magelearn_productsgrid/tag/save"/>
                </item>
            </argument>
        </argument>
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
            </item>
        </argument>
    </dataSource>
    <fieldset name="data">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="label" xsi:type="string"/>
                <item name="collapsible" xsi:type="boolean">false</item>
            </item>
        </argument>
        <container name="messages">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="label" xsi:type="string"/>
                    <item name="component" xsi:type="string">Magento_Catalog/js/components/messages</item>
                </item>
            </argument>
        </container>
        <field name="name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="label" xsi:type="string" translate="true">Tag Name</item>
                    <item name="componentType" xsi:type="string">field</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="dataScope" xsi:type="string">data.name</item>
                    <item name="dataType" xsi:type="string">string</item>
                    <item name="sortOrder" xsi:type="number">10</item>
                    <item name="required" xsi:type="boolean">true</item>
                    <item name="validation" xsi:type="array">
                        <item name="required-entry" xsi:type="boolean">true</item>
                    </item>
                </item>
            </argument>
        </field>
    </fieldset>
</form>

As per highlighted code above add the file at Block/Adminhtml/Tag/Edit/Button/CreateTag.php

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Tag\Edit\Button;

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

/**
 * Button "Create Tag" in "New Post" slide-out panel of a Item page
 * Class CreateTag
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Tag\Edit\Button
 */
class CreateTag implements ButtonProviderInterface
{
    /**
     * @return array
     */
    public function getButtonData()
    {
        return [
            'label' => __('Create Tag'),
            'class' => 'save primary',
            'data_attribute' => [
                'mage-init' => ['button' => ['event' => 'save']],
                'form-role' => 'save',
            ],
            'sort_order' => 10
        ];
    }
}

Also, add a file at Ui/DataProvider/Item/Form/NewTagDataProvider.php

<?php

namespace Magelearn\ProductsGrid\Ui\DataProvider\Item\Form;

use Magento\Framework\UrlInterface;
use Magento\Ui\DataProvider\AbstractDataProvider;
use Magelearn\ProductsGrid\Model\ResourceModel\Tag\CollectionFactory;

/**
 * DataProvider for new tag form
 */
class NewTagDataProvider extends AbstractDataProvider
{
    /**
     * @var UrlInterface
     */
    protected $urlBuilder;

    /**
     * @param string $name
     * @param string $primaryFieldName
     * @param string $requestFieldName
     * @param CollectionFactory $collectionFactory
     * @param UrlInterface $urlBuilder
     * @param array $meta
     * @param array $data
     */
    public function __construct(
        $name,
        $primaryFieldName,
        $requestFieldName,
        CollectionFactory $collectionFactory,
        UrlInterface $urlBuilder,
        array $meta = [],
        array $data = []
    ) {
        parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);

        $this->collection = $collectionFactory->create();
        $this->urlBuilder = $urlBuilder;
    }

    /**
     * {@inheritdoc}
     */
    public function getData()
    {
        $this->data = array_replace_recursive(
            $this->data,
            [
                'config' => [
                    'data' => [
                        'is_active' => 1,
                        'include_in_menu' => 1,
                        'return_session_messages_only' => 1,
                        'use_config' => ['available_sort_by', 'default_sort_by']
                    ]
                ]
            ]
        );

        return $this->data;
    }
}

Add Save tag file at Controller/Adminhtml/Tag/Save.php

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Tag;

use Exception;
use Magento\Backend\App\Action\Context;
use Magento\Backend\Helper\Js;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\Result\Json;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Controller\Result\Redirect;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\AlreadyExistsException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Message\MessageInterface;
use Magento\Framework\Registry;
use Magento\Framework\View\Element\Messages;
use Magento\Framework\View\LayoutFactory;
use Magelearn\ProductsGrid\Controller\Adminhtml\Tag;
use Magelearn\ProductsGrid\Model\TagFactory;
use Magelearn\ProductsGrid\Controller\Adminhtml\PostDataProcessor;
use Psr\Log\LoggerInterface;
use RuntimeException;

/**
 * Class Save
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Tag
 */
class Save extends Tag
{
    /**
     * @var Js
     */
    public $jsHelper;

    /**
     * Layout Factory
     *
     * @var LayoutFactory
     */
    public $layoutFactory;

    /**
     * Result Json Factory
     *
     * @var JsonFactory
     */
    public $resultJsonFactory;
    
    /**
     * DataProcessor
     *
     * @var PostDataProcessor
     */
    public $dataProcessor;
    
    protected $dataPersistor;

    /**
     * Save constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param Js $jsHelper
     * @param LayoutFactory $layoutFactory
     * @param JsonFactory $resultJsonFactory
     * @param PostDataProcessor $dataProcessor,
     * @param \Magento\Framework\App\Request\DataPersistorInterface $dataPersistor
     * @param TagFactory $tagFactory
     */
    public function __construct(
        Context $context,
        Registry $registry,
        Js $jsHelper,
        LayoutFactory $layoutFactory,
        JsonFactory $resultJsonFactory,
        PostDataProcessor $dataProcessor,
        \Magento\Framework\App\Request\DataPersistorInterface $dataPersistor,
        TagFactory $tagFactory
    ) {
        $this->jsHelper = $jsHelper;
        $this->layoutFactory = $layoutFactory;
        $this->resultJsonFactory = $resultJsonFactory;
        $this->dataProcessor = $dataProcessor;
        $this->dataPersistor = $dataPersistor;

        parent::__construct($context, $registry, $tagFactory);
    }

    /**
     * @return $this|ResponseInterface|Redirect|ResultInterface
     */
    public function execute()
    {
        if ($this->getRequest()->getPost('return_session_messages_only')) {
            $tag = $this->initTag();
            $tagPostData = $this->getRequest()->getPostValue();
            $tagPostData['enabled'] = 1;

            $tag->addData($tagPostData);

            try {
                if ($this->dataProcessor->validate($tagPostData, $validate = 'tag')) {
                    $tag->save();
                    $this->messageManager->addSuccessMessage(__('You saved the tag.'));
                }
            } catch (AlreadyExistsException $e) {
                $this->messageManager->addErrorMessage($e->getMessage());
                $this->_objectManager->get(LoggerInterface::class)->critical($e);
            } catch (LocalizedException $e) {
                $this->messageManager->addErrorMessage($e->getMessage());
                $this->_objectManager->get(LoggerInterface::class)->critical($e);
            } catch (Exception $e) {
                $this->messageManager->addErrorMessage(__('Something went wrong while saving the tag.'));
                $this->_objectManager->get(LoggerInterface::class)->critical($e);
            }

            $hasError = (bool)$this->messageManager->getMessages()->getCountByType(
                MessageInterface::TYPE_ERROR
            );
            
            $tag->load($tag->getId());
            $tag->addData([
                'level' => 1,
                'entity_id' => $tag->getId(),
                'is_active' => $tag->getEnabled(),
                'parent' => 0
            ]);

            /** @var $block Messages */
            $block = $this->layoutFactory->create()->getMessagesBlock();
            $block->setMessages($this->messageManager->getMessages(true));

            /** @var Json $resultJson */
            $resultJson = $this->resultJsonFactory->create();

            return $resultJson->setData([
                'messages' => $block->getGroupedHtml(),
                'error' => $hasError,
                'category' => $tag->toArray()
            ]);
        }

        $resultRedirect = $this->resultRedirectFactory->create();
        $data = $this->getRequest()->getPost('tag');
        if ($data) {
            if (!$this->dataProcessor->validate($data, $validate = 'tag')) {
                $this->dataPersistor->set('magelearn_item_tag', $data);
                if (!empty($data['category_id'])) {
                    return $resultRedirect->setPath(
                        'magelearn_productsgrid/*/edit',
                        [
                            'id' => $data['tag_id'],
                            '_current' => true
                        ]
                        );
                } else {
                    return $resultRedirect->setPath(
                        'magelearn_productsgrid/*/edit',
                        [
                            '_current' => true
                        ]
                        );
                }
            }
            /** @var \Magelearn\ProductsGrid\Model\Tag $tag */
            $tag = $this->initTag();

            $tag->addData($data);
            if ($posts = $this->getRequest()->getPost('posts', false)) {
                $tag->setPostsData($this->jsHelper->decodeGridSerializedInput($posts));
            }

            $this->_eventManager->dispatch(
                'magelearn_item_tag_prepare_save',
                ['tag' => $tag, 'request' => $this->getRequest()]
            );

            try {
                $tag->save();

                $this->messageManager->addSuccessMessage(__('The Tag has been saved.'));
                $this->_session->setData('magelearn_item_tag_data', false);
                $this->dataPersistor->clear('magelearn_item_tag');

                if ($this->getRequest()->getParam('back')) {
                    $resultRedirect->setPath('magelearn_productsgrid/*/edit', ['id' => $tag->getId(), '_current' => true]);
                } else {
                    $resultRedirect->setPath('magelearn_productsgrid/*/');
                }

                return $resultRedirect;
            } catch (LocalizedException $e) {
                $this->messageManager->addErrorMessage($e->getMessage());
            } catch (RuntimeException $e) {
                $this->messageManager->addErrorMessage($e->getMessage());
            } catch (Exception $e) {
                $this->messageManager->addExceptionMessage($e, __('Something went wrong while saving the Tag.'));
            }
            $this->_getSession()->setData('magelearn_item_tag_data', $data);
            $this->dataPersistor->set('magelearn_item_tag', $data);
            $resultRedirect->setPath('magelearn_productsgrid/*/edit', ['id' => $tag->getId(), '_current' => true]);
            return $resultRedirect;
        }

        $resultRedirect->setPath('magelearn_productsgrid/*/');

        return $resultRedirect;
    }
}

As per the highlighted code in the above file, add Controller/Adminhtml/PostDataProcessor.php file.

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml;

use Magelearn\ProductsGrid\Model\CategoryFactory;
use Magelearn\ProductsGrid\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory;
use Magelearn\ProductsGrid\Model\TagFactory;
use Magelearn\ProductsGrid\Model\ResourceModel\Tag\CollectionFactory as TagCollectionFactory;
use Magento\Framework\Message\ManagerInterface;
use Magento\Framework\Stdlib\DateTime\Filter\Date;
use Magento\Framework\View\Model\Layout\Update\ValidatorFactory;

class PostDataProcessor
{
    /**
     * Date Filter
     *
     * @var Date
     */
    protected $dateFilter;

    /**
     * Validation Factory
     *
     * @var ValidatorFactory
     */
    protected $validatorFactory;

    /**
     * Message Manager
     *
     * @var ManagerInterface
     */
    protected $messageManager;

    /**
     * CategoryCollectionFactory
     *
     * @var CategoryCollectionFactory
     */
    protected $categoryCollectionFactory;
    
    /**
     * TagCollectionFactory
     *
     * @var TagCollectionFactory
     */
    protected $tagCollectionFactory;

    /**
     * PostDataProcessor Class constructor
     *
     * @param Date               $dateFilter         DateFiletr
     * @param ManagerInterface   $messageManager     MessageManager
     * @param ValidatorFactory   $validatorFactory   ValidationFactory
     * @param CategoryCollectionFactory  $categoryCollectionFactory
     * @param TagCollectionFactory  $tagCollectionFactory
     */
    public function __construct(
        Date $dateFilter,
        ManagerInterface $messageManager,
        ValidatorFactory $validatorFactory,
        CategoryCollectionFactory $categoryCollectionFactory,
        TagCollectionFactory  $tagCollectionFactory
    ) {
        $this->dateFilter = $dateFilter;
        $this->messageManager = $messageManager;
        $this->validatorFactory = $validatorFactory;
        $this->categoryCollectionFactory = $categoryCollectionFactory;
        $this->tagCollectionFactory = $tagCollectionFactory;
    }

    /**
     * Validate post data
     *
     * @param array $data Datapost
     *
     * @return bool
     */
    public function validate($data, $validate)
    {
        $errorNo1 = $this->validateRequireEntry($data);
        $errorNo2 = $this->checkNameExist($data, $validate);
        $errorNo3 = true;

        if (!in_array($data['enabled'], [0,1]) || $data['enabled'] == '' || $data['enabled'] === null) {
            $errorNo3 = false;
            $this->messageManager->addErrorMessage(
                __("Please enter valid status.")
            );
        }

        return $errorNo1 && $errorNo2 && $errorNo3;
    }    

    /**
     * Check if required fields is not empty
     *
     * @param array $data RequireFields
     *
     * @return bool
     */
    public function validateRequireEntry(array $data)
    {
        $requiredFields = [
            'name' => __('Name')
        ];

        $errorNo = true;
        foreach ($data as $field => $value) {
            if (in_array($field, array_keys($requiredFields)) && $value == '') {
                $errorNo = false;
                $this->messageManager->addErrorMessage(
                    __(
                        'To apply changes you should fill valid value to required "%1" field',
                        $requiredFields[$field]
                    )
                );
            }
        }
        return $errorNo;
    }

    /**
     * Check if name is already exist or not
     *
     * @param array $data RequireFields
     *
     * @return bool
     */
    public function checkNameExist(array $data, String $validate)
    {
        $errorNo = true;
        
        if($validate == 'category') {
            if (isset($data['category_id'])) {
                $prapareCollection = $this->categoryCollectionFactory->create()
                ->addFieldToFilter('category_id', ['neq' => $data['category_id']]);
            } else {
                $prapareCollection = $this->categoryCollectionFactory->create();
            }
        }
        
        if($validate == 'tag') {
            if (isset($data['tag_id'])) {
                $prapareCollection = $this->tagCollectionFactory->create()
                ->addFieldToFilter('tag_id', ['neq' => $data['tag_id']]);
            } else {
                $prapareCollection = $this->tagCollectionFactory->create();
            }
        }
        
        foreach ($prapareCollection as $collection) {
            $collectionName = trim(mb_strtolower(preg_replace('/\s+/', ' ', $collection->getName()), 'UTF-8'));
            if (trim(preg_replace('/\s+/', ' ', mb_strtolower($data['name'], 'UTF-8'))) == $collectionName) {
                $errorNo = false;
                $this->messageManager->addErrorMessage(
                    __('This name is already exist.')
                );
            }
        }
        return $errorNo;
    }
}

Now as per the highlighted code in Block/Adminhtml/Post/Edit/Tab/Renderer/Category.php file,

Add view/adminhtml/layout/magelearn_productsgrid_category_create.xml file.

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="styles"/>
    <body>
        <referenceContainer name="content">
            <uiComponent name="item_new_category_form"/>
        </referenceContainer>
    </body>
</page>

As per the highlighted in the above file add view/adminhtml/ui_component/item_new_category_form.xml

<?xml version="1.0" encoding="UTF-8"?>
<form 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">item_new_category_form.new_category_form_data_source</item>
            <item name="deps" xsi:type="string">item_new_category_form.new_category_form_data_source</item>
            <item name="namespace" xsi:type="string">item_new_category_form</item>
        </item>
        <item name="buttons" xsi:type="array">
            <item name="save" xsi:type="string">Magelearn\ProductsGrid\Block\Adminhtml\Category\Edit\Button\CreateCategory</item>
        </item>
        <item name="template" xsi:type="string">templates/form/collapsible</item>
        <item name="config" xsi:type="array">
            <item name="ajaxSaveType" xsi:type="string">simple</item>
        </item>
    </argument>
    <dataSource name="new_category_form_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Magelearn\ProductsGrid\Ui\DataProvider\Item\Form\NewCategoryDataProvider</argument>
            <argument name="name" xsi:type="string">new_category_form_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">entity_id</argument>
            <argument name="requestFieldName" xsi:type="string">id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="submit_url" xsi:type="url" path="magelearn_productsgrid/category/save"/>
                </item>
            </argument>
        </argument>
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
            </item>
        </argument>
    </dataSource>
    <fieldset name="data">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="label" xsi:type="string"/>
                <item name="collapsible" xsi:type="boolean">false</item>
            </item>
        </argument>
        <container name="messages">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="label" xsi:type="string"/>
                    <item name="component" xsi:type="string">Magento_Catalog/js/components/messages</item>
                </item>
            </argument>
        </container>
        <field name="name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="label" xsi:type="string" translate="true">Category Name</item>
                    <item name="componentType" xsi:type="string">field</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="dataScope" xsi:type="string">data.name</item>
                    <item name="dataType" xsi:type="string">string</item>
                    <item name="sortOrder" xsi:type="number">10</item>
                    <item name="required" xsi:type="boolean">true</item>
                    <item name="validation" xsi:type="array">
                        <item name="required-entry" xsi:type="boolean">true</item>
                    </item>
                </item>
            </argument>
        </field>
    </fieldset>
</form>

As per the highlighted in the above file add Block/Adminhtml/Category/Edit/Button/CreateCategory.php

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Category\Edit\Button;

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

/**
 * Button "Create Tag" in "New Post" slide-out panel of a Item page
 * Class CreateTag
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Category\Edit\Button
 */
class CreateCategory implements ButtonProviderInterface
{
    /**
     * @return array
     */
    public function getButtonData()
    {
        return [
            'label' => __('Create Category'),
            'class' => 'save primary',
            'data_attribute' => [
                'mage-init' => ['button' => ['event' => 'save']],
                'form-role' => 'save',
            ],
            'sort_order' => 10
        ];
    }
}

Also, add Ui/DataProvider/Item/Form/NewCategoryDataProvider.php file.

<?php

namespace Magelearn\ProductsGrid\Ui\DataProvider\Item\Form;

use Magento\Framework\Phrase;
use Magento\Framework\UrlInterface;
use Magento\Ui\DataProvider\AbstractDataProvider;
use Magelearn\ProductsGrid\Model\ResourceModel\Category\CollectionFactory;

/**
 * DataProvider for new category form
 */
class NewCategoryDataProvider extends AbstractDataProvider
{
    /**
     * @var UrlInterface
     */
    protected $urlBuilder;

    /**
     * @param string $name
     * @param string $primaryFieldName
     * @param string $requestFieldName
     * @param CollectionFactory $collectionFactory
     * @param UrlInterface $urlBuilder
     * @param array $meta
     * @param array $data
     */
    public function __construct(
        $name,
        $primaryFieldName,
        $requestFieldName,
        CollectionFactory $collectionFactory,
        UrlInterface $urlBuilder,
        array $meta = [],
        array $data = []
    ) {
        parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);

        $this->collection = $collectionFactory->create();
        $this->urlBuilder = $urlBuilder;
    }

    /**
     * {@inheritdoc}
     */
    public function getData()
    {
        $this->data = array_replace_recursive(
            $this->data,
            [
                'config' => [
                    'data' => [
                        'is_active' => 1,
                        'include_in_menu' => 1,
                        'return_session_messages_only' => 1,
                        'use_config' => ['available_sort_by', 'default_sort_by']
                    ]
                ]
            ]
        );

        return $this->data;
    }
}

Add file at Controller/Adminhtml/Category/Save.php

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Category;

use Exception;
use Magento\Backend\App\Action\Context;
use Magento\Backend\Helper\Js;
use Magento\Catalog\Model\Category as CategoryModel;
use Magento\Framework\Controller\Result\Json;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Controller\Result\RawFactory;
use Magento\Framework\Controller\Result\Redirect;
use Magento\Framework\Exception\AlreadyExistsException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Message\MessageInterface;
use Magento\Framework\Registry;
use Magento\Framework\View\Element\Messages;
use Magento\Framework\View\LayoutFactory;
use Magelearn\ProductsGrid\Controller\Adminhtml\Category;
use Magelearn\ProductsGrid\Model\CategoryFactory;
use Magelearn\ProductsGrid\Controller\Adminhtml\PostDataProcessor;
use Psr\Log\LoggerInterface;

/**
 * Class Save
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Category
 */
class Save extends Category
{
    /**
     * Result Raw Factory
     *
     * @var RawFactory
     */
    public $resultRawFactory;

    /**
     * Result Json Factory
     *
     * @var JsonFactory
     */
    public $resultJsonFactory;

    /**
     * Layout Factory
     *
     * @var LayoutFactory
     */
    public $layoutFactory;
    
    /**
     * PostDataProcessor
     *
     * @var PostDataProcessor
     */
    public $dataProcessor;
    
    protected $dataPersistor;

    /**
     * JS helper
     *
     * @var Js
     */
    public $jsHelper;

    /**
     * Save constructor.
     *
     * @param Context $context
     * @param Registry $coreRegistry
     * @param CategoryFactory $categoryFactory
     * @param RawFactory $resultRawFactory
     * @param JsonFactory $resultJsonFactory
     * @param LayoutFactory $layoutFactory
     * @param PostDataProcessor $dataProcessor
     * @param \Magento\Framework\App\Request\DataPersistorInterface $dataPersistor
     * @param Js $jsHelper
     */
    public function __construct(
        Context $context,
        Registry $coreRegistry,
        CategoryFactory $categoryFactory,
        RawFactory $resultRawFactory,
        JsonFactory $resultJsonFactory,
        LayoutFactory $layoutFactory,
        PostDataProcessor $dataProcessor,
        \Magento\Framework\App\Request\DataPersistorInterface $dataPersistor,
        Js $jsHelper
    ) {
        $this->resultRawFactory = $resultRawFactory;
        $this->resultJsonFactory = $resultJsonFactory;
        $this->layoutFactory = $layoutFactory;
        $this->dataProcessor = $dataProcessor;
        $this->dataPersistor = $dataPersistor;
        $this->jsHelper = $jsHelper;

        parent::__construct($context, $coreRegistry, $categoryFactory);
    }

    /**
     * @return Json|Redirect
     */
    public function execute()
    {
        if ($this->getRequest()->getPost('return_session_messages_only')) {
            $category = $this->initCategory();
            $categoryPostData = $this->getRequest()->getPostValue();
            $categoryPostData['enabled'] = 1;

            $category->addData($categoryPostData);

            try {
                if ($this->dataProcessor->validate($categoryPostData, $validate = 'category')) {
                    $category->save();
                    $this->messageManager->addSuccessMessage(__('You saved the category.'));
                }
            } catch (AlreadyExistsException $e) {
                $this->messageManager->addErrorMessage($e->getMessage());
                $this->_objectManager->get(LoggerInterface::class)->critical($e);
            } catch (LocalizedException $e) {
                $this->messageManager->addErrorMessage($e->getMessage());
                $this->_objectManager->get(LoggerInterface::class)->critical($e);
            } catch (Exception $e) {
                $this->messageManager->addErrorMessage(__('Something went wrong while saving the category.'));
                $this->_objectManager->get(LoggerInterface::class)->critical($e);
            }

            $hasError = (bool)$this->messageManager->getMessages()->getCountByType(
                MessageInterface::TYPE_ERROR
            );
            
            $category->load($category->getId());
            $category->addData([
                'level' => 1,
                'entity_id' => $category->getId(),
                'is_active' => $category->getEnabled(),
                'parent' => 0
            ]);

            // to obtain truncated category name
            /** @var $block Messages */
            $block = $this->layoutFactory->create()->getMessagesBlock();
            $block->setMessages($this->messageManager->getMessages(true));

            /** @var Json $resultJson */
            $resultJson = $this->resultJsonFactory->create();

            return $resultJson->setData(
                [
                    'messages' => $block->getGroupedHtml(),
                    'error' => $hasError,
                    'category' => $category->toArray()
                ]
            );
        }

        $resultRedirect = $this->resultRedirectFactory->create();
        $data = $this->getRequest()->getPost('category');
        if ($data) {
            if (!$this->dataProcessor->validate($data, $validate = 'category')) {
                $this->dataPersistor->set('magelearn_item_category', $data);
                if (!empty($data['category_id'])) {
                    return $resultRedirect->setPath(
                        'magelearn_productsgrid/*/edit',
                        [
                            'id' => $data['category_id'],
                            '_current' => true
                        ]
                        );
                } else {
                    return $resultRedirect->setPath(
                        'magelearn_productsgrid/*/edit',
                        [
                            '_current' => true
                        ]
                        );
                }
            }
            $category = $this->initCategory();

            if (!$category) {
                $resultRedirect->setPath('magelearn_productsgrid/*/', ['_current' => true]);

                return $resultRedirect;
            }

            $category->addData($data);
            if ($posts = $this->getRequest()->getPost('posts', false)) {
                $category->setPostsData($this->jsHelper->decodeGridSerializedInput($posts));
            }

            $this->_eventManager->dispatch(
                'magelearn_productsgrid_category_prepare_save',
                ['category' => $category, 'request' => $this->getRequest()]
            );

            try {
                $category->save();
                $this->messageManager->addSuccessMessage(__('You saved the Category.'));
                $this->_getSession()->setData('magelearn_productsgrid_category_data', false);
                $this->dataPersistor->clear('magelearn_item_category');
                
                if ($this->getRequest()->getParam('back')) {
                    $resultRedirect->setPath('magelearn_productsgrid/*/edit', ['id' => $category->getId(), '_current' => true]);
                } else {
                    $resultRedirect->setPath('magelearn_productsgrid/*/');
                }
                return $resultRedirect;
            } catch (Exception $e) {
                $this->messageManager->addErrorMessage($e->getMessage());
                $this->_getSession()->setData('magelearn_productsgrid_category_data', $data);
            }
            $this->_getSession()->setData('magelearn_productsgrid_category_data', $data);
            $this->dataPersistor->set('magelearn_item_category', $data);
            $resultRedirect->setPath('magelearn_productsgrid/*/edit', ['_current' => true, 'id' => $category->getId()]);
            return $resultRedirect;
        }

        $resultRedirect->setPath('magelearn_productsgrid/*/edit', ['_current' => true]);

        return $resultRedirect;
    }
}

Now to display products in the Post Edit tab,

Add file at Magelearn/ProductsGrid/Block/Adminhtml/Post/Edit/Tab/Product.php

<?php

namespace Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit\Tab;

use Exception;
use Magento\Backend\Block\Template\Context;
use Magento\Backend\Block\Widget\Grid\Column;
use Magento\Backend\Block\Widget\Grid\Extended;
use Magento\Backend\Block\Widget\Tab\TabInterface;
use Magento\Backend\Helper\Data;
use Magento\Catalog\Model\ResourceModel\Product\Collection;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Registry;
use Magelearn\ProductsGrid\Model\Tag;

/**
 * Class Product
 * @package Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit\Tab
 */
class Product extends Extended implements TabInterface
{
    /**
     * @var CollectionFactory
     */
    public $productCollectionFactory;

    /**
     * @var Registry
     */
    public $coreRegistry;

    /**
     * @var RequestInterface
     */
    protected $request;

    /**
     * Product constructor.
     *
     * @param Context $context
     * @param Registry $coreRegistry
     * @param Data $backendHelper
     * @param RequestInterface $request
     * @param CollectionFactory $productCollectionFactory
     * @param array $data
     */
    public function __construct(
        Context $context,
        Registry $coreRegistry,
        Data $backendHelper,
        RequestInterface $request,
        CollectionFactory $productCollectionFactory,
        array $data = []
    ) {
        $this->productCollectionFactory = $productCollectionFactory;
        $this->coreRegistry             = $coreRegistry;
        $this->request                  = $request;

        parent::__construct($context, $backendHelper, $data);
    }

    /**
     * Set grid params
     */
    public function _construct()
    {
        parent::_construct();

        $this->setId('product_grid');
        $this->setDefaultSort('position');
        $this->setDefaultDir('ASC');
        $this->setSaveParametersInSession(false);
        $this->setUseAjax(true);
        if ($this->getPost()->getId()) {
            $this->setDefaultFilter(['in_products' => 1]);
        }
    }

    /**
     * @inheritdoc
     */
    protected function _prepareCollection()
    {
        /** @var Collection $collection */
        $collection = $this->productCollectionFactory->create();
        $collection->clear();

        $collection->getSelect()->joinLeft(
            ['mp_p' => $collection->getTable('magelearn_item_post_product')],
            'e.entity_id = mp_p.entity_id',
            ['position']
        )->group('e.entity_id');

        $this->setCollection($collection);

        return parent::_prepareCollection();
    }

    /**
     * @return $this
     * @throws Exception
     */
    protected function _prepareColumns()
    {
        $this->addColumn('in_products', [
            'header_css_class' => 'a-center',
            'type'             => 'checkbox',
            'name'             => 'in_product',
            'values'           => $this->_getSelectedProducts(),
            'align'            => 'center',
            'index'            => 'entity_id'
        ]);
        $this->addColumn('entity_id', [
            'header'           => __('ID'),
            'sortable'         => true,
            'index'            => 'entity_id',
            'type'             => 'number',
            'header_css_class' => 'col-id',
            'column_css_class' => 'col-id'
        ]);
        $this->addColumn('title', [
            'header'           => __('Sku'),
            'index'            => 'sku',
            'header_css_class' => 'col-name',
            'column_css_class' => 'col-name'
        ]);

        if ($this->request->getParam('item_id') || $this->getPost()->getId()) {
            $this->addColumn('position', [
                'header' => __('Position'),
                'name' => 'position',
                'width' => 60,
                'type' => 'number',
                'validate_class' => 'validate-number',
                'index' => 'position',
                'editable' => true,
                'edit_only' => true,
            ]);
        } else {
            $this->addColumn('position', [
                'header' => __('Position'),
                'name' => 'position',
                'width' => 60,
                'type' => 'number',
                'validate_class' => 'validate-number',
                'index' => 'position',
                'editable' => true,
                'filter' => false,
                'edit_only' => true,
            ]);
        }

        return $this;
    }

    /**
     * Retrieve selected Tags
     *
     * @return array
     */
    protected function _getSelectedProducts()
    {
        $products = $this->getRequest()->getPost('post_products', null);
        if (!is_array($products)) {
            $products = $this->getPost()->getProductsPosition();

            return array_keys($products);
        }

        return $products;
    }

    /**
     * Retrieve selected Tags
     *
     * @return array
     */
    public function getSelectedProducts()
    {
        $selected = $this->getPost()->getProductsPosition();
        if (!is_array($selected)) {
            $selected = [];
        } else {
            foreach ($selected as $key => $value) {
                $selected[$key] = ['position' => $value];
            }
        }

        return $selected;
    }

    /**
     * @param Tag|Object $item
     *
     * @return string
     */
    public function getRowUrl($item)
    {
        return '#';
    }

    /**
     * get grid url
     *
     * @return string
     */
    public function getGridUrl()
    {
        return $this->getUrl('*/*/productsGrid', ['item_id' => $this->getPost()->getId()]);
    }

    /**
     * @return \Magelearn\ProductsGrid\Model\Post
     */
    public function getPost()
    {
        return $this->coreRegistry->registry('magelearn_item_post');
    }

    /**
     * @param Column $column
     *
     * @return $this
     * @throws LocalizedException
     */
    protected function _addColumnFilterToCollection($column)
    {
        if ($column->getId() === 'in_products') {
            $productIds = $this->_getSelectedProducts();
            if (empty($productIds)) {
                $productIds = 0;
            }
            if ($column->getFilter()->getValue()) {
                $this->getCollection()->addFieldToFilter('entity_id', ['in' => $productIds]);
            } else {
                if ($productIds) {
                    $this->getCollection()->addFieldToFilter('entity_id', ['nin' => $productIds]);
                }
            }
        } else {
            parent::_addColumnFilterToCollection($column);
        }

        return $this;
    }

    /**
     * @return string
     */
    public function getTabLabel()
    {
        return __('Products');
    }

    /**
     * @return bool
     */
    public function isHidden()
    {
        return false;
    }

    /**
     * @return string
     */
    public function getTabTitle()
    {
        return $this->getTabLabel();
    }

    /**
     * @return bool
     */
    public function canShowTab()
    {
        return true;
    }

    /**
     * @return string
     */
    public function getTabUrl()
    {
        return $this->getUrl('magelearn_productsgrid/post/products', ['_current' => true]);
    }

    /**
     * @return string
     */
    public function getTabClass()
    {
        return 'ajax only';
    }
}

Now add view/adminhtml/layout/magelearn_productsgrid_post_products.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" output="1">
        <block class="Magelearn\ProductsGrid\Block\Adminhtml\Post\Edit\Tab\Product" name="post.edit.tab.product"/>
        <block class="Magento\Backend\Block\Widget\Grid\Serializer" name="product_grid_serializer">
            <arguments>
                <argument name="input_names" xsi:type="string">position</argument>
                <argument name="grid_block" xsi:type="string">post.edit.tab.product</argument>
                <argument name="callback" xsi:type="string">getSelectedProducts</argument>
                <argument name="input_element_name" xsi:type="string">products</argument>
                <argument name="reload_param_name" xsi:type="string">post_products</argument>
            </arguments>
        </block>
    </container>
</layout>

And view/adminhtml/layout/magelearn_productsgrid_post_productsgrid.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\ProductsGrid\Block\Adminhtml\Post\Edit\Tab\Product" name="post.edit.tab.product"/>
    </container>
</layout>

Also, add Controller/Adminhtml/Post/Products.php file.

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Post;

use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Registry;
use Magento\Framework\View\Result\LayoutFactory;
use Magelearn\ProductsGrid\Controller\Adminhtml\Post;
use Magelearn\ProductsGrid\Model\PostFactory;

/**
 * Class Products
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Post
 */
class Products extends Post
{
    /**
     * @var LayoutFactory
     */
    protected $resultLayoutFactory;

    /**
     * Products constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param PostFactory $productFactory
     * @param LayoutFactory $resultLayoutFactory
     */
    public function __construct(
        Context $context,
        Registry $coreRegistry,
        PostFactory $productFactory,
        LayoutFactory $resultLayoutFactory
    ) {
        parent::__construct($productFactory, $context, $coreRegistry);

        $this->resultLayoutFactory = $resultLayoutFactory;
    }

    /**
     * Save action
     *
     * @return ResultInterface
     */
    public function execute()
    {
        $this->initPost(true);

        return $this->resultLayoutFactory->create();
    }
}

And Controller/Adminhtml/Post/ProductsGrid.php file.

<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Post;

/**
 * Class ProductsGrid
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Post
 */
class ProductsGrid extends Products
{
}
To Save the Post data, we will add Controller/Adminhtml/Post/Save.php file.
<?php

namespace Magelearn\ProductsGrid\Controller\Adminhtml\Post;

use Exception;
use Magento\Backend\App\Action\Context;
use Magento\Backend\Helper\Js;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\Result\Redirect;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\DataObject;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Registry;
use Magento\Framework\Stdlib\DateTime\DateTime;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magelearn\ProductsGrid\Controller\Adminhtml\Post;
use Magelearn\ProductsGrid\Helper\Data;
use Magelearn\ProductsGrid\Helper\Image;
use Magelearn\ProductsGrid\Model\Post as PostModel;
use Magelearn\ProductsGrid\Model\PostFactory;
use Magelearn\ProductsGrid\Api\Data\SwatchInterface;
use Magelearn\ProductsGrid\Api\Data\SwatchInterfaceFactory;
use Magelearn\ProductsGrid\Model\SwatchRepository;
use RuntimeException;

/**
 * Class Save
 * @package Magelearn\ProductsGrid\Controller\Adminhtml\Post
 */
class Save extends Post
{
    /**
     * JS helper
     *
     * @var Js
     */
    public $jsHelper;

    /**
     * @var DateTime
     */
    public $date;

    /**
     * @var Image
     */
    protected $imageHelper;

    /**
     * @var Data
     */
    protected $_helperData;

    /**
     * @var TimezoneInterface
     */
    protected $timezone;
    
    /**
     * @var SwatchRepository
     */
    protected $swatchRepository;
    
    /**
    * @var SwatchInterfaceFactory
    */
    protected $swatchFactory;

    /**
     * Save constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param PostFactory $postFactory
     * @param Js $jsHelper
     * @param Image $imageHelper
     * @param Data $helperData
     * @param DateTime $date
     * @param TimezoneInterface $timezone
     * @param SwatchRepository $swatchRepository
     * @param SwatchInterfaceFactory $swatchFactory
     */
    public function __construct(
        Context $context,
        Registry $registry,
        PostFactory $postFactory,
        Js $jsHelper,
        Image $imageHelper,
        Data $helperData,
        DateTime $date,
        TimezoneInterface $timezone,
        SwatchRepository $swatchRepository,
        SwatchInterfaceFactory $swatchFactory
    ) {
        $this->jsHelper         = $jsHelper;
        $this->_helperData      = $helperData;
        $this->imageHelper      = $imageHelper;
        $this->date             = $date;
        $this->timezone         = $timezone;
        $this->swatchRepository = $swatchRepository;
        $this->swatchFactory    = $swatchFactory;

        parent::__construct($postFactory, $context, $registry);
    }

    /**
     * @return ResponseInterface|Redirect|ResultInterface
     * @throws LocalizedException
     */
    public function execute()
    {
        $resultRedirect = $this->resultRedirectFactory->create();
        $action         = $this->getRequest()->getParam('action');

        if ($data = $this->getRequest()->getPost('post')) {
            /** @var PostModel $post */
            $post = $this->initPost(false, true);
            $this->prepareData($post, $data);

            $this->_eventManager->dispatch(
                'magelearn_item_post_prepare_save',
                ['post' => $post, 'request' => $this->getRequest()]
            );

            try {
                if (empty($action) || $action === 'add') {
                    $post->save();
                    $this->messageManager->addSuccessMessage(__('The post has been saved.'));
                }

                $this->_getSession()->setData('magelearn_item_post_data', false);

                if ($this->getRequest()->getParam('back')) {
                    $resultRedirect->setPath('magelearn_productsgrid/*/edit', ['id' => $post->getId(), '_current' => true]);
                } else {
                    $resultRedirect->setPath('magelearn_productsgrid/*/');
                }

                return $resultRedirect;
            } catch (RuntimeException $e) {
                $this->messageManager->addErrorMessage($e->getMessage());
            } catch (Exception $e) {
                $this->messageManager->addExceptionMessage($e, __('Something went wrong while saving the Post.'));
            }

            $this->_getSession()->setData('magelearn_item_post_data', $data);

            $resultRedirect->setPath('magelearn_productsgrid/*/edit', ['id' => $post->getId(), '_current' => true]);

            return $resultRedirect;
        }

        $resultRedirect->setPath('magelearn_productsgrid/*/');

        return $resultRedirect;
    }

    /**
     * @param PostModel $post
     * @param array $data
     *
     * @return $this
     * @throws LocalizedException
     */
    protected function prepareData($post, $data = [])
    {
        if (!$this->getRequest()->getParam('image')) {
            try {
                $this->imageHelper->uploadImage($data, 'image', Image::TEMPLATE_MEDIA_TYPE_POST, $post->getImage());
            } catch (Exception $exception) {
                $data['image'] = isset($data['image']['value']) ? $data['image']['value'] : '';
            }
        } else {
            $data['image'] = '';
        }

        /** Set specify field data */
        $data['categories_ids'] = (isset($data['categories_ids']) && $data['categories_ids']) ? explode(
            ',',
            $data['categories_ids'] ?? ''
        ) : [];
        $data['tags_ids'] = (isset($data['tags_ids']) && $data['tags_ids'])
            ? explode(',', $data['tags_ids'] ?? '') : [];

        if ($post->getCreatedAt() == null) {
            $data['created_at'] = $this->date->date();
        }
        $data['updated_at'] = $this->date->date();

        $post->addData($data);
        
        $swatches = $this->getRequest()->getPost('option');
        $postId = $this->getRequest()->getParam('id');
        if(isset($swatches) && !empty($swatches) && is_array($swatches)) {
            foreach ($swatches as $id => $swatch) {
                if (is_numeric($id)) {
                    $this->updateSwatch($swatch, $id);
                } else {
                    $this->createSwatch($swatch, $postId);
                }
            }
        }

        if ($tags = $this->getRequest()->getPost('tags', false)) {
            $post->setTagsData(
                $this->jsHelper->decodeGridSerializedInput($tags)
            );
        }

        $products = $this->getRequest()->getPost('products', false);

        if ($products || $products === '') {
            $post->setProductsData(
                $this->jsHelper->decodeGridSerializedInput($products)
            );
        } else {
            $productData = [];
            foreach ($post->getProductsPosition() as $key => $value) {
                $productData[$key] = ['position' => $value];
            }
            $post->setProductsData($productData);
        }

        return $this;
    }
    /**
     * @param array $swatchData
     * @param $id
     */
    protected function updateSwatch(array $swatchData, $id)
    {
        try {
            if (isset($swatchData['delete']) && $swatchData['delete']) {
                $this->swatchRepository->deleteById($id);
                return;
            }
            
            $swatch = $this->swatchRepository->getById($id);
            $swatch->setPosition($swatchData['order'])
            ->setDescription($swatchData['description'] ?? '')
            ->setImage($swatchData['image']);
            $this->swatchRepository->save($swatch);
        } catch (\Magento\Framework\Exception\LocalizedException $exception) {
        }
    }
    
    /**
     * @param array $swatchData
     * @param $postId
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    protected function createSwatch(array $swatchData, $postId)
    {
        $swatch = $this->swatchFactory->create();
        $swatch->setPosition($swatchData['order'])
        ->setItemId($postId)
        ->setPosition(0)
        ->setImage($swatchData['image'])
        ->setDescription($swatchData['description'] ?? '');
        $this->swatchRepository->save($swatch);
    }
}

And last we will also add our CSS file at view/adminhtml/web/css/source/_module.less and necessary Image folder.

0 Comments On "Add Products Grid and Custom Collection in UI Component form in custom module Magento2"

Back To Top