Magento2 | PWA | GraphQL

Advanced FAQ Management with FAQ's category image upload and multiple category assignment for FAQ - Magento2


This Advanced Magento2 FAQ module provides below options for FAQ Management:

1. Add FAQ categories by providing a category icon upload button.

2. Add FAQ by assigning multiple categories to FAQ questions.

3. Create categories on the fly from Add Question Grid.

You can find the complete module on GitHub at Magelearn_CategoryfaqAdvanced

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






Let's Start by creating a custom extension.

Create a folder inside app/code/Magelearn/Categoryfaq

Add registration.php file in it:
<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(
    ComponentRegistrar::MODULE,
    'Magelearn_Categoryfaq',
    __DIR__
);
Add composer.json file in it:
{
    "name": "magelearn/module-categoryfaq",
    "description": "Magento 2 Category FAQ Extension",
    "type": "magento2-module",
    "license": "GPL-3.0",
    "authors": [
        {
            "name": "vijay rami",
            "email": "vijaymrami@gmail.com"
        }
    ],
    "minimum-stability": "dev",
    "require": {},
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Magelearn\\Categoryfaq\\": ""
        }
    }
}
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_Categoryfaq" setup_version="1.0.0">
    </module>
</config>
Now to manage the FAQ in the Database, we will create three different tables. To manage questions, categories and question categories.

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_categoryfaq_question" resource="default" engine="innodb" comment="magelearn_categoryfaq_question Table">
        <column name="question_id" padding="10" xsi:type="int" nullable="false" unsigned="true" comment="ID" identity="true"/>
        <column name="title" nullable="false" xsi:type="text" comment="Question Title"/>
        <column name="answer" nullable="true" xsi:type="text" comment="Answer for the question"/>
        <column name="status" nullable="true" xsi:type="int" comment="Question Display Status" identity="false"/>
        <column name="sort_order" nullable="false" default="0" xsi:type="int" comment="Question Display Position" identity="false"/>
        <column name="created_at" nullable="true" xsi:type="datetime" comment="Date of creation" default="CURRENT_TIMESTAMP"/>
        <column name="updated_at" nullable="true" xsi:type="datetime" comment="Last update date" default="CURRENT_TIMESTAMP" on_update="true"/>
        <constraint xsi:type="primary" referenceId="PRIMARY">
            <column name="question_id"/>
        </constraint>
        <index referenceId="MAGELEARN_CATEGORYFAQ_QUESTION_TITLE"
            indexType="fulltext">
            <column name="title"/>
        </index>
        <index referenceId="MAGELEARN_CATEGORYFAQ_QUESTION_ANSWER"
            indexType="fulltext">
            <column name="answer"/>
        </index>
    </table>
    <table name="magelearn_categoryfaq_category" resource="default" engine="innodb" comment="magelearn_categoryfaq_category Table">
        <column name="category_id" xsi:type="int" padding="10" unsigned="true" nullable="false" identity="true"  comment="ID"/>
        <column name="name" nullable="false" xsi:type="text" comment="Name"/>
        <column name="description" nullable="true" xsi:type="text" comment="Description"/>
        <column name="icon" nullable="true" xsi:type="text" comment="Category Icon"/>
        <column name="sort_order" nullable="false" xsi:type="int" default="0" comment="Sort Order Position" identity="false"/>
        <column name="status" nullable="true" xsi:type="int" comment="Category Display Status" identity="false"/>
        <column name="created_at" nullable="true" xsi:type="datetime" comment="Date of creation of this category" default="CURRENT_TIMESTAMP"/>
        <column name="updated_at" nullable="true" xsi:type="datetime" comment="Last update date of this category" default="CURRENT_TIMESTAMP" on_update="true"/>
        <index referenceId="MAGELEARN_CATEGORYFAQ_CATEGORY_CTR_ID"
            indexType="btree">
            <column name="category_id"/>
        </index>
        <constraint xsi:type="primary" referenceId="PRIMARY">
            <column name="category_id"/>
        </constraint>
        <index referenceId="MAGELEARN_CATEGORYFAQ_NAME"
            indexType="fulltext">
            <column name="name"/>
        </index>
        <index referenceId="MAGELEARN_CATEGORYFAQ_DESCRIPTION"
            indexType="fulltext">
            <column name="description"/>
        </index>
    </table>
    <table name="magelearn_categoryfaq_question_category" resource="default" engine="innodb" comment="Category To Question 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="question_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Question 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="question_id"/>
    </constraint>
    <constraint xsi:type="foreign" referenceId="MAGELEARN_CATEGORYFAQ_QUESTION_CTGR_CTGR_ID_MAGELEARN_CATEGORYFAQ_CTGR_CTGR_ID"
        table="magelearn_categoryfaq_question_category"
        column="category_id"
        referenceTable="magelearn_categoryfaq_category"
        referenceColumn="category_id" onDelete="CASCADE"/>
    <constraint xsi:type="foreign" referenceId="MAGELEARN_CATEGORYFAQ_QUESTION_CTGR_QUESTION_ID_MAGELEARN_CATEGORYFAQ_QUESTION_QUESTION_ID"
        table="magelearn_categoryfaq_question_category"
        column="question_id"
        referenceTable="magelearn_categoryfaq_question"
        referenceColumn="question_id" onDelete="CASCADE"/>
    <constraint xsi:type="unique" referenceId="MAGELEARN_CATEGORYFAQ_QUESTION_CATEGORY_CATEGORY_ID_QUESTION_ID">
      <column name="category_id"/>
      <column name="question_id"/>
    </constraint>
    <index referenceId="MAGELEARN_CATEGORYFAQ_QUESTION_CATEGORY_CATEGORY_ID"
        indexType="btree">
      <column name="category_id"/>
    </index>
    <index referenceId="MAGELEARN_CATEGORYFAQ_QUESTION_CATEGORY_QUESTION_ID" 
        indexType="btree">
      <column name="question_id"/>
    </index>
  </table>
</schema>

Now, we will add our Data patch file. This file will add some dummy data into database table.

Create CreateDefaultSamples.php file inside Setup/Patch/Data folder.

<?php

namespace Magelearn\Categoryfaq\Setup\Patch\Data;

use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;

class CreateDefaultSamples implements DataPatchInterface
{
    /**
     * @var ModuleDataSetupInterface
     */
    private $moduleDataSetup;

    public function __construct(
       ModuleDataSetupInterface $moduleDataSetup

     ) {

        $this->moduleDataSetup = $moduleDataSetup;

    }
    public function apply()
    {
        $this->moduleDataSetup->startSetup();
        $setup = $this->moduleDataSetup;

        $category_data = [
                    [
                        'name' => __('Default'),
                        'description' => __('When there is only 1 active category, only its questions will be displayed.'),
                        'sort_order'  => 0,
                        'status'      => 1
                    ]
                ];

         $this->moduleDataSetup->getConnection()->insertArray(
            $this->moduleDataSetup->getTable('magelearn_categoryfaq_category'),
            ['name', 'description','sort_order','status'],
             $category_data
         );     
         
         $question_data = [
             [
                 'title'  => __('What should I use this module for?'),
                 'answer' => __('Category FAQ module was designed as a simple solution to provide information for your customers'),
                 'status' => 1,
                 'sort_order'  => 0
             ]
         ];
         $this->moduleDataSetup->getConnection()->insertArray(
             $this->moduleDataSetup->getTable('magelearn_categoryfaq_question'),
             ['title', 'answer','status','sort_order'],
             $question_data
             );

         $question_category_data = [
             [
                 'category_id'  => 1,
                 'question_id' => 1,
                 'position' => 1
             ]
         ];
         $this->moduleDataSetup->getConnection()->insertArray(
             $this->moduleDataSetup->getTable('magelearn_categoryfaq_question_category'),
             ['category_id', 'question_id','position'],
             $question_category_data
             );
         
        $this->moduleDataSetup->endSetup();
    }
    public function getAliases()
    {
        return [];
    }
    /**
     * @inheritdoc
     */
    public function revert()
    {
        $this->moduleDataSetup->getConnection()->startSetup();
        
        $this->moduleDataSetup->getConnection()->endSetup();
    }
    public static function getDependencies()
    {
        return [];
    }
}

To un-install our custom table we will create Uninstall script.

Create Uninstall.php file inside Setup folder.

<?php

namespace Magelearn\Categoryfaq\Setup;

use Magento\Framework\Model\AbstractModel;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\Setup\UninstallInterface;
use Magento\Config\Model\ResourceModel\Config\Data\CollectionFactory;

/**
 * Class Uninstall
 *
 * @package Magelearn\Categoryfaq\Setup
 */
class Uninstall implements UninstallInterface
{
    /**
     * @var CollectionFactory
     */
    protected $collectionFactory;

    /**
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(
        CollectionFactory $collectionFactory
    ) {
        $this->collectionFactory = $collectionFactory;
    }

    /**
     * Drop sample table
     *
     * @param SchemaSetupInterface $setup
     * @param ModuleContextInterface $context
     */
    public function uninstall(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        if ($setup->tableExists('magelearn_categoryfaq_category')) {
            $setup->getConnection()->dropTable('magelearn_categoryfaq_category');
        }
        if ($setup->tableExists('magelearn_categoryfaq_question')) {
            $setup->getConnection()->dropTable('magelearn_categoryfaq_question');
        }
        if ($setup->tableExists('magelearn_categoryfaq_question_category')) {
            $setup->getConnection()->dropTable('magelearn_categoryfaq_question_category');
        }
    }
}

Now, create di.xml file inside etc folder. 

This file will define necessary nodes to display data in admin grid and image upload functionality for category icon.

<?xml version="1.0" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magelearn\Categoryfaq\Api\CategoryRepositoryInterface" type="Magelearn\Categoryfaq\Model\CategoryRepository"/>
    <preference for="Magelearn\Categoryfaq\Api\Data\CategoryInterface" type="Magelearn\Categoryfaq\Model\Category"/>
    <preference for="Magelearn\Categoryfaq\Api\Data\CategorySearchResultsInterface" type="Magento\Framework\Api\SearchResults"/>
    <virtualType name="Magelearn\Categoryfaq\Model\ResourceModel\Category\Grid\Collection" type="Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult">
        <arguments>
            <argument name="mainTable" xsi:type="string">magelearn_categoryfaq_category</argument>
            <argument name="resourceModel" xsi:type="string">Magelearn\Categoryfaq\Model\ResourceModel\Category\Collection</argument>
        </arguments>
    </virtualType>
    <type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
        <arguments>
            <argument name="collections" xsi:type="array">
                <item name="magelearn_categoryfaq_category_listing_data_source" xsi:type="string">Magelearn\Categoryfaq\Model\ResourceModel\Category\Grid\Collection</item>
                <item name="magelearn_categoryfaq_question_listing_data_source" xsi:type="string">Magelearn\Categoryfaq\Model\ResourceModel\Question\Grid\Collection</item>
            </argument>
        </arguments>
    </type>
    <preference for="Magelearn\Categoryfaq\Api\QuestionRepositoryInterface" type="Magelearn\Categoryfaq\Model\QuestionRepository"/>
    <preference for="Magelearn\Categoryfaq\Api\Data\QuestionInterface" type="Magelearn\Categoryfaq\Model\Question"/>
    <preference for="Magelearn\Categoryfaq\Api\Data\QuestionSearchResultsInterface" type="Magento\Framework\Api\SearchResults"/>
    <virtualType name="Magelearn\Categoryfaq\Model\ResourceModel\Question\Grid\Collection" type="Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult">
        <arguments>
            <argument name="mainTable" xsi:type="string">magelearn_categoryfaq_question</argument>
            <argument name="resourceModel" xsi:type="string">Magelearn\Categoryfaq\Model\ResourceModel\Question\Collection</argument>
        </arguments>
    </virtualType>
    <virtualType name="Magelearn\Categoryfaq\FaqCategoryImageUpload" type="Magelearn\Categoryfaq\Model\ImageUploader">
            <arguments>
                    <argument name="baseTmpPath" xsi:type="string">faq/tmp/icon</argument>
                    <argument name="basePath" xsi:type="string">faq/icon</argument>
                    <argument name="allowedExtensions" xsi:type="array">
                        <item name="jpg" xsi:type="string">jpg</item>
                        <item name="jpeg" xsi:type="string">jpeg</item>
                        <item name="gif" xsi:type="string">gif</item>
                        <item name="png" xsi:type="string">png</item>
                    </argument>
            </arguments>
        </virtualType>
    <type name="Magelearn\Categoryfaq\Controller\Adminhtml\Category\Upload">
        <arguments>
                <argument name="imageUploader" xsi:type="object">Magelearn\Categoryfaq\FaqCategoryImageUpload</argument>
        </arguments>
    </type>
</config>

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_Categoryfaq::management" title="FAQ" module="Magelearn_Categoryfaq" parent="Magento_Backend::content" sortOrder="30" resource="Magelearn_Categoryfaq::management"/>
        <add id="Magelearn_Categoryfaq::magelearn_categoryfaq_question" title="Questions" module="Magelearn_Categoryfaq" sortOrder="9999" resource="Magelearn_Categoryfaq::magelearn_categoryfaq_question" parent="Magelearn_Categoryfaq::management" action="magelearn_categoryfaq/question/index"/>
        <add id="Magelearn_Categoryfaq::magelearn_categoryfaq_category" title="Categories" module="Magelearn_Categoryfaq" sortOrder="9999" resource="Magelearn_Categoryfaq::magelearn_categoryfaq_category" parent="Magelearn_Categoryfaq::management" action="magelearn_categoryfaq/category/index"/>
    </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 frontName="magelearn_categoryfaq" id="magelearn_categoryfaq">
            <module before="Magento_Backend" name="Magelearn_Categoryfaq"/>
        </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_Categoryfaq::question" title="question" sortOrder="10">
                    <resource id="Magelearn_Categoryfaq::question_save" title="Save question" sortOrder="10"/>
                    <resource id="Magelearn_Categoryfaq::question_delete" title="Delete question" sortOrder="20"/>
                    <resource id="Magelearn_Categoryfaq::question_update" title="Update question" sortOrder="30"/>
                    <resource id="Magelearn_Categoryfaq::question_view" title="View question" sortOrder="40"/>
                </resource>
                <resource id="Magelearn_Categoryfaq::category" title="category" sortOrder="20">
                    <resource id="Magelearn_Categoryfaq::category_save" title="Save category" sortOrder="10"/>
                    <resource id="Magelearn_Categoryfaq::category_delete" title="Delete category" sortOrder="20"/>
                    <resource id="Magelearn_Categoryfaq::category_update" title="Update category" sortOrder="30"/>
                    <resource id="Magelearn_Categoryfaq::category_view" title="View category" sortOrder="40"/>
                </resource>
                
            </resource>
        </resources>
    </acl>
</config>

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

Create CategoryInterface.php file inside Api/Data folder.
<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Api\Data;

interface CategoryInterface
{
    const STATUS = 'status';
    const NAME = 'name';
    const DESCRIPTION = 'description';
    const ICON = 'icon';
    const SORT_ORDER = 'sort_order';
    const CATEGORY_ID = 'category_id';
    const CREATED_AT = 'created_at';
    const UPDATED_AT = 'updated_at';

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

    /**
     * Set id
     * @param int $id
     * @return \Magelearn\Categoryfaq\Category\Api\Data\CategoryInterface
     */
    public function setId($id);

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

    /**
     * Set name
     * @param string $name
     * @return \Magelearn\Categoryfaq\Category\Api\Data\CategoryInterface
     */
    public function setName($name);

    /**
     * Get description
     * @return string|null
     */
    public function getDescription();

    /**
     * Set description
     * @param string $description
     * @return \Magelearn\Categoryfaq\Category\Api\Data\CategoryInterface
     */
    public function setDescription($description);
    
    /**
     * Get icon
     * @return string|null
     */
    public function getIcon();
    
    /**
     * Set icon
     * @param string $icon
     * @return \Magelearn\Categoryfaq\Api\Data\CategoryInterface
     */
    public function setIcon($icon);
    
    /**
     * Get sort_order
     * @return int/null
     */
    public function getSortOrder();

    /**
     * Set sort_order
     * @param int $sortOrder
     * @return \Magelearn\Categoryfaq\Category\Api\Data\CategoryInterface
     */
    public function setSortOrder($sortOrder);

    /**
     * Get status
     * @return int/null
     */
    public function getStatus();

    /**
     * Set status
     * @param int $status
     * @return \Magelearn\Categoryfaq\Category\Api\Data\CategoryInterface
     */
    public function setStatus($status);

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

    /**
     * Set created_at
     * @param string $createdAt
     * @return \Magelearn\Categoryfaq\Category\Api\Data\CategoryInterface
     */
    public function setCreatedAt($createdAt);

    /**
     * Get updated_at
     * @return string|null
     */
    public function getUpdatedAt();

    /**
     * Set updated_at
     * @param string $updatedAt
     * @return \Magelearn\Categoryfaq\Category\Api\Data\CategoryInterface
     */
    public function setUpdatedAt($updatedAt);
}

Create CategorySearchResultsInterface.php file inside Api/Data folder.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Api\Data;

interface CategorySearchResultsInterface extends \Magento\Framework\Api\SearchResultsInterface
{

    /**
     * Get category list.
     * @return \Magelearn\Categoryfaq\Api\Data\CategoryInterface[]
     */
    public function getItems();

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

Create CategoryRepositoryInterface.php file inside Api folder.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Api;

use Magento\Framework\Api\SearchCriteriaInterface;

interface CategoryRepositoryInterface
{

    /**
     * Save category
     * @param \Magelearn\Categoryfaq\Api\Data\CategoryInterface $category
     * @return \Magelearn\Categoryfaq\Api\Data\CategoryInterface
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function save(
        \Magelearn\Categoryfaq\Api\Data\CategoryInterface $category
    );

    /**
     * Retrieve category
     * @param string $categoryId
     * @return \Magelearn\Categoryfaq\Api\Data\CategoryInterface
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function get($categoryId);

    /**
     * Retrieve category matching the specified criteria.
     * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
     * @return \Magelearn\Categoryfaq\Api\Data\CategorySearchResultsInterface
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function getList(
        \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
    );

    /**
     * Delete category
     * @param \Magelearn\Categoryfaq\Api\Data\CategoryInterface $category
     * @return bool true on success
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function delete(
        \Magelearn\Categoryfaq\Api\Data\CategoryInterface $category
    );

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

Create Model/Category.php file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Model;

use Magelearn\Categoryfaq\Api\Data\CategoryInterface;
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 Magento\Framework\Stdlib\DateTime\DateTimeFactory;

class Category extends AbstractModel implements CategoryInterface
{
    /**
     * @var DateTimeFactory
     */
    private $dateTimeFactory;
    
    public function __construct(
        Context $context,
        Registry $registry,
        DateTimeFactory $dateTimeFactory,
        AbstractResource $resource = null,
        AbstractDb $resourceCollection = null,
        array $data = []
        ) {
            parent::__construct($context, $registry, $resource, $resourceCollection, $data);
            $this->dateTimeFactory = $dateTimeFactory;
    }
    /**
     * @inheritDoc
     */
    public function _construct()
    {
        $this->_init(\Magelearn\Categoryfaq\Model\ResourceModel\Category::class);
    }

    /**
     * @inheritDoc
     */
    public function getId()
    {
        return $this->getData(self::CATEGORY_ID);
    }

    /**
     * @inheritDoc
     */
    public function setId($id)
    {
        return $this->setData(self::CATEGORY_ID, $id);
    }

    /**
     * @inheritDoc
     */
    public function getName()
    {
        return $this->getData(self::NAME);
    }

    /**
     * @inheritDoc
     */
    public function setName($name)
    {
        return $this->setData(self::NAME, $name);
    }

    /**
     * @inheritDoc
     */
    public function getDescription()
    {
        return $this->getData(self::DESCRIPTION);
    }

    /**
     * @inheritDoc
     */
    public function setDescription($description)
    {
        return $this->setData(self::DESCRIPTION, $description);
    }
    
    /**
     * @inheridoc
     */
    public function getIcon()
    {
        return $this->getData(self::ICON);
    }
    
    /**
     * @inheridoc
     */
    public function setIcon($icon)
    {
        return $this->setData(self::ICON, $icon);
    }

    /**
     * @inheritDoc
     */
    public function getSortOrder()
    {
        return $this->getData(self::SORT_ORDER);
    }

    /**
     * @inheritDoc
     */
    public function setSortOrder($sortOrder)
    {
        return $this->setData(self::SORT_ORDER, $sortOrder);
    }

    /**
     * @inheritDoc
     */
    public function getStatus()
    {
        return $this->getData(self::STATUS);
    }

    /**
     * @inheritDoc
     */
    public function setStatus($status)
    {
        return $this->setData(self::STATUS, $status);
    }

    /**
     * @inheritDoc
     */
    public function getCreatedAt()
    {
        return $this->getData(self::CREATED_AT);
    }

    /**
     * @inheritDoc
     */
    public function setCreatedAt($createdAt)
    {
        return $this->setData(self::CREATED_AT, $createdAt);
    }

    /**
     * @inheritDoc
     */
    public function getUpdatedAt()
    {
        return $this->getData(self::UPDATED_AT);
    }

    /**
     * @inheritDoc
     */
    public function setUpdatedAt($updatedAt)
    {
        return $this->setData(self::UPDATED_AT, $updatedAt);
    }
    /**
     * Set updated_at before saving
     *
     * @return AbstractModel
     */
    public function beforeSave()
    {
        if ($this->getId()) {
            $this->setUpdatedAt($this->dateTimeFactory->create()->gmtDate());
        }
        
        return parent::beforeSave();
    }
}

Create Model/CategoryRepository.php file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Model;

use Magelearn\Categoryfaq\Api\CategoryRepositoryInterface;
use Magelearn\Categoryfaq\Api\Data\CategoryInterface;
use Magelearn\Categoryfaq\Api\Data\CategoryInterfaceFactory;
use Magelearn\Categoryfaq\Api\Data\CategorySearchResultsInterfaceFactory;
use Magelearn\Categoryfaq\Model\ResourceModel\Category as ResourceCategory;
use Magelearn\Categoryfaq\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory;
use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface;
use Magento\Framework\Exception\CouldNotDeleteException;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\NoSuchEntityException;

class CategoryRepository implements CategoryRepositoryInterface
{

    /**
     * @var CategoryInterfaceFactory
     */
    protected $categoryFactory;

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

    /**
     * @var ResourceCategory
     */
    protected $resource;

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

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


    /**
     * @param ResourceCategory $resource
     * @param CategoryInterfaceFactory $categoryFactory
     * @param CategoryCollectionFactory $categoryCollectionFactory
     * @param CategorySearchResultsInterfaceFactory $searchResultsFactory
     * @param CollectionProcessorInterface $collectionProcessor
     */
    public function __construct(
        ResourceCategory $resource,
        CategoryInterfaceFactory $categoryFactory,
        CategoryCollectionFactory $categoryCollectionFactory,
        CategorySearchResultsInterfaceFactory $searchResultsFactory,
        CollectionProcessorInterface $collectionProcessor
    ) {
        $this->resource = $resource;
        $this->categoryFactory = $categoryFactory;
        $this->categoryCollectionFactory = $categoryCollectionFactory;
        $this->searchResultsFactory = $searchResultsFactory;
        $this->collectionProcessor = $collectionProcessor;
    }

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

    /**
     * @inheritDoc
     */
    public function get($categoryId)
    {
        $category = $this->categoryFactory->create();
        $this->resource->load($category, $categoryId);
        if (!$category->getId()) {
            throw new NoSuchEntityException(__('category with id "%1" does not exist.', $categoryId));
        }
        return $category;
    }

    /**
     * @inheritDoc
     */
    public function getList(
        \Magento\Framework\Api\SearchCriteriaInterface $criteria
    ) {
        $collection = $this->categoryCollectionFactory->create();
        
        $this->collectionProcessor->process($criteria, $collection);
        
        $searchResults = $this->searchResultsFactory->create();
        $searchResults->setSearchCriteria($criteria);
        
        $items = [];
        foreach ($collection as $model) {
            $items[] = $model;
        }
        
        $searchResults->setItems($items);
        $searchResults->setTotalCount($collection->getSize());
        return $searchResults;
    }

    /**
     * @inheritDoc
     */
    public function delete(CategoryInterface $category)
    {
        try {
            $categoryModel = $this->categoryFactory->create();
            $this->resource->load($categoryModel, $category->getCategoryId());
            $this->resource->delete($categoryModel);
        } catch (\Exception $exception) {
            throw new CouldNotDeleteException(__(
                'Could not delete the category: %1',
                $exception->getMessage()
            ));
        }
        return true;
    }

    /**
     * @inheritDoc
     */
    public function deleteById($categoryId)
    {
        return $this->delete($this->get($categoryId));
    }
}

Create Model/ResourceModel/Category.php file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Model\ResourceModel;

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

class Category extends AbstractDb
{

    /**
     * @inheritDoc
     */
    protected function _construct()
    {
        $this->_init('magelearn_categoryfaq_category', 'category_id');
    }
}

Create Model/ResourceModel/Category/Collection.php file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Model\ResourceModel\Category;

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

/**
 * Class Collection
 * @package Magelearn\Categoryfaq\Model\ResourceModel\Category
 */
class Collection extends AbstractCollection
{
    /**
     * @inheritDoc
     */
    protected $_idFieldName = 'category_id';
    
    /**
     * Event prefix
     *
     * @var string
     */
    protected $_eventPrefix = 'magelearn_categoryfaq_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);
    }
    
    /**
     * @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;
    }
}

Create QuestionInterface.php file inside Api/Data folder.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Api\Data;

interface QuestionInterface
{
    const QUESTION_ID = 'question_id';
    const TITLE = 'title';
    const ANSWER = 'answer';
    const STATUS = 'status';
    const SORT_ORDER = 'sort_order';
    const CATEGORY_IDS = 'category_ids';
    const CREATED_AT = 'created_at';
    const UPDATED_AT = 'updated_at';
    
    /**
     * Get Question id
     * @return int|null
     */
    public function getQuestionId();

    /**
     * Set Question id
     * @param int $id
     * @return \Magelearn\Categoryfaq\Question\Api\Data\QuestionInterface
     */
    public function setQuestionId($questionid);

    /**
     * Get title
     * @return string|null
     */
    public function getTitle();

    /**
     * Set title
     * @param string $title
     * @return \Magelearn\Categoryfaq\Question\Api\Data\QuestionInterface
     */
    public function setTitle($title);

    /**
     * Get answer
     * @return string|null
     */
    public function getAnswer();

    /**
     * Set answer
     * @param string $answer
     * @return \Magelearn\Categoryfaq\Question\Api\Data\QuestionInterface
     */
    public function setAnswer($answer);

    /**
     * Get status
     * @return int|null
     */
    public function getStatus();

    /**
     * Set status
     * @param int $status
     * @return \Magelearn\Categoryfaq\Question\Api\Data\QuestionInterface
     */
    public function setStatus($status);

    /**
     * Get sort_order
     * @return int|null
     */
    public function getSortOrder();

    /**
     * Set sort_order
     * @param int $sortOrder
     * @return \Magelearn\Categoryfaq\Question\Api\Data\QuestionInterface
     */
    public function setSortOrder($sortOrder);

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

    /**
     * Set created_at
     * @param string $createdAt
     * @return \Magelearn\Categoryfaq\Question\Api\Data\QuestionInterface
     */
    public function setCreatedAt($createdAt);

    /**
     * Get updated_at
     * @return string|null
     */
    public function getUpdatedAt();

    /**
     * Set updated_at
     * @param string $updatedAt
     * @return \Magelearn\Categoryfaq\Question\Api\Data\QuestionInterface
     */
    public function setUpdatedAt($updatedAt);
    
    /**
     * @return int[]|null
     */
    public function getCategoryIds();
}

Create QuestionSearchResultsInterface.php file inside Api/Data folder.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Api\Data;

interface QuestionSearchResultsInterface extends \Magento\Framework\Api\SearchResultsInterface
{

    /**
     * Get question list.
     * @return \Magelearn\Categoryfaq\Api\Data\QuestionInterface[]
     */
    public function getItems();

    /**
     * Set id list.
     * @param \Magelearn\Categoryfaq\Api\Data\QuestionInterface[] $items
     * @return $this
     */
    public function setItems(array $items = null);
}

Create QuestionRepositoryInterface.php file inside Api folder.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Api;

use Magento\Framework\Api\SearchCriteriaInterface;

interface QuestionRepositoryInterface
{

    /**
     * Save question
     * @param \Magelearn\Categoryfaq\Api\Data\QuestionInterface $question
     * @return \Magelearn\Categoryfaq\Api\Data\QuestionInterface
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function save(
        \Magelearn\Categoryfaq\Api\Data\QuestionInterface $question
    );

    /**
     * Retrieve question
     * @param string $questionId
     * @return \Magelearn\Categoryfaq\Api\Data\QuestionInterface
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function get($questionId);

    /**
     * Retrieve question matching the specified criteria.
     * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
     * @return \Magelearn\Categoryfaq\Api\Data\QuestionSearchResultsInterface
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function getList(
        \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
    );

    /**
     * Delete question
     * @param \Magelearn\Categoryfaq\Api\Data\QuestionInterface $question
     * @return bool true on success
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function delete(
        \Magelearn\Categoryfaq\Api\Data\QuestionInterface $question
    );

    /**
     * Delete question by ID
     * @param string $questionId
     * @return bool true on success
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function deleteById($questionId);
    
    /**
     * @param string $questionId
     *
     * @return \Magelearn\Categoryfaq\Api\Data\CategoryInterface[]
     */
    public function getCategoriesByQuestionId($questionId);
}

Create Model/Question.php file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Model;

use Magelearn\Categoryfaq\Api\Data\QuestionInterface;
use Magento\Framework\Data\Collection\AbstractDb;
use Magento\Framework\Model\AbstractModel;
use Magento\Framework\Model\Context;
use Magento\Framework\Model\ResourceModel\AbstractResource;
use Magelearn\Categoryfaq\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory;
use Magento\Framework\Registry;
use Magento\Framework\Stdlib\DateTime\DateTimeFactory;

class Question extends AbstractModel implements QuestionInterface {
    
    /**
     * @var DateTimeFactory
     */
    private $dateTimeFactory;
    
    /**
     * Faq Category Collection
     *
     * @var ResourceModel\Category\Collection
     */
    public $categoryCollection;
    
    /**
     * Faq Category Collection Factory
     *
     * @var CategoryCollectionFactory
     */
    public $categoryCollectionFactory;
    
    public function __construct(
        Context $context,
        Registry $registry,
        DateTimeFactory $dateTimeFactory,
        CategoryCollectionFactory $categoryCollectionFactory,
        AbstractResource $resource = null,
        AbstractDb $resourceCollection = null,
        array $data = []
        ) {
            
            parent::__construct($context, $registry, $resource, $resourceCollection, $data);
            $this->dateTimeFactory = $dateTimeFactory;
            $this->categoryCollectionFactory = $categoryCollectionFactory;
    }

    /**
     * @inheritDoc
     */
    public function _construct()
    {
        $this->_init(\Magelearn\Categoryfaq\Model\ResourceModel\Question::class);
    }

    /**
     * @inheritDoc
     */
    public function getQuestionId()
    {
        return $this->getData(self::QUESTION_ID);
    }

    /**
     * @inheritDoc
     */
    public function setQuestionId($questionid)
    {
        return $this->setData(self::QUESTION_ID, $questionid);
    }
    
    /**
     * get entity default values
     *
     * @return array
     */
    public function getDefaultValues()
    {
        $values             = [];
        $values['status']   = '1';
        
        return $values;
    }

    /**
     * @inheritDoc
     */
    public function getTitle()
    {
        return $this->getData(self::TITLE);
    }

    /**
     * @inheritDoc
     */
    public function setTitle($title)
    {
        return $this->setData(self::TITLE, $title);
    }

    /**
     * @inheritDoc
     */
    public function getAnswer()
    {
        $answer = $this->getData(self::ANSWER);
        
        $maxLength = 200;
        if ($answer && strlen($answer) > $maxLength) {
            $answer = substr($answer, 0, $maxLength) . '...';
        }
        
        return $answer;
    }

    /**
     * @inheritDoc
     */
    public function setAnswer($answer)
    {
        return $this->setData(self::ANSWER, $answer);
    }

    /**
     * @inheritDoc
     */
    public function getStatus()
    {
        return $this->getData(self::STATUS);
    }

    /**
     * @inheritDoc
     */
    public function setStatus($status)
    {
        return $this->setData(self::STATUS, $status);
    }

    /**
     * @inheritDoc
     */
    public function getSortOrder()
    {
        return $this->getData(self::SORT_ORDER);
    }

    /**
     * @inheritDoc
     */
    public function setSortOrder($sortOrder)
    {
        return $this->setData(self::SORT_ORDER, $sortOrder);
    }
    
    /**
     * @return ResourceModel\Category\Collection
     */
    public function getSelectedCategoriesCollection()
    {
        if ($this->categoryCollection === null) {
            $collection = $this->categoryCollectionFactory->create();
            $collection->join(
                $this->getResource()->getTable('magelearn_categoryfaq_question_category'),
                'main_table.category_id=' . $this->getResource()->getTable('magelearn_categoryfaq_question_category') .
                '.category_id AND ' . $this->getResource()->getTable('magelearn_categoryfaq_question_category') . '.question_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');
    }

    /**
     * @inheritDoc
     */
    public function getCreatedAt()
    {
        return $this->getData(self::CREATED_AT);
    }

    /**
     * @inheritDoc
     */
    public function setCreatedAt($createdAt)
    {
        return $this->setData(self::CREATED_AT, $createdAt);
    }

    /**
     * @inheritDoc
     */
    public function getUpdatedAt()
    {
        return $this->getData(self::UPDATED_AT);
    }

    /**
     * @inheritDoc
     */
    public function setUpdatedAt($updatedAt)
    {
        return $this->setData(self::UPDATED_AT, $updatedAt);
    }
    /**
     * Set updated_at before saving
     *
     * @return AbstractModel
     */
    public function beforeSave()
    {
        if ($this->getId()) {
            $this->setUpdatedAt($this->dateTimeFactory->create()->gmtDate());
        }
        
        return parent::beforeSave();
    }
}

Create Model/QuestionRepository.php file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Model;

use Magelearn\Categoryfaq\Api\Data\QuestionInterface;
use Magelearn\Categoryfaq\Api\Data\QuestionInterfaceFactory;
use Magelearn\Categoryfaq\Api\Data\QuestionSearchResultsInterfaceFactory;
use Magelearn\Categoryfaq\Api\QuestionRepositoryInterface;
use Magento\Framework\Stdlib\DateTime\DateTime;
use Magelearn\Categoryfaq\Model\ResourceModel\Question as ResourceQuestion;
use Magelearn\Categoryfaq\Model\ResourceModel\Question\CollectionFactory as QuestionCollectionFactory;
use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface;
use Magento\Framework\Exception\CouldNotDeleteException;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\NoSuchEntityException;

class QuestionRepository implements QuestionRepositoryInterface
{

    /**
     * @var ResourceQuestion
     */
    protected $resource;

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

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

    /**
     * @var QuestionInterfaceFactory
     */
    protected $questionFactory;

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


    /**
     * @param ResourceQuestion $resource
     * @param QuestionInterfaceFactory $questionFactory
     * @param QuestionCollectionFactory $questionCollectionFactory
     * @param QuestionSearchResultsInterfaceFactory $searchResultsFactory
     * @param CollectionProcessorInterface $collectionProcessor
     * @param DateTime $date
     */
    public function __construct(
        ResourceQuestion $resource,
        QuestionInterfaceFactory $questionFactory,
        QuestionCollectionFactory $questionCollectionFactory,
        QuestionSearchResultsInterfaceFactory $searchResultsFactory,
        CollectionProcessorInterface $collectionProcessor,
        DateTime $date
    ) {
        $this->resource = $resource;
        $this->questionFactory = $questionFactory;
        $this->questionCollectionFactory = $questionCollectionFactory;
        $this->searchResultsFactory = $searchResultsFactory;
        $this->collectionProcessor = $collectionProcessor;
        $this->date = $date;
    }

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

    /**
     * @inheritDoc
     */
    public function get($questionId)
    {
        $question = $this->questionFactory->create();
        $this->resource->load($question, $questionId);
        if (!$question->getId()) {
            throw new NoSuchEntityException(__('question with id "%1" does not exist.', $questionId));
        }
        return $question;
    }

    /**
     * @inheritDoc
     */
    public function getList(
        \Magento\Framework\Api\SearchCriteriaInterface $criteria
    ) {
        $collection = $this->questionCollectionFactory->create();
        
        $this->collectionProcessor->process($criteria, $collection);
        
        $searchResults = $this->searchResultsFactory->create();
        $searchResults->setSearchCriteria($criteria);
        
        $items = [];
        foreach ($collection as $model) {
            $items[] = $model;
        }
        
        $searchResults->setItems($items);
        $searchResults->setTotalCount($collection->getSize());
        return $searchResults;
    }

    /**
     * @inheritDoc
     */
    public function delete(QuestionInterface $question)
    {
        try {
            $questionModel = $this->questionFactory->create();
            $this->resource->load($questionModel, $question->getQuestionId());
            $this->resource->delete($questionModel);
        } catch (\Exception $exception) {
            throw new CouldNotDeleteException(__(
                'Could not delete the question: %1',
                $exception->getMessage()
            ));
        }
        return true;
    }

    /**
     * @inheritDoc
     */
    public function deleteById($questionId)
    {
        return $this->delete($this->get($questionId));
    }
    /**
     * @param array $data
     */
    protected function prepareData(&$data)
    {
        if (!empty($data['categories_ids'])) {
            $data['categories_ids'] = explode(',', $data['categories_ids']);
        }
        
        if (empty($data['status'])) {
            $data['status'] = 0;
        }
        
        $data['created_at'] = $this->date->date();
    }
    
    /**
     * @inheritDoc
     */
    public function getCategoriesByQuestionId($questionId)
    {
        $question = $this->questionFactory->create()->load($questionId);
        
        return $question->getSelectedCategoriesCollection()->getItems();
    }
}

Create Model/ResourceModel/Question.php file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Model\ResourceModel;

use Magento\Backend\Model\Auth;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Event\ManagerInterface;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
use Magento\Framework\Model\AbstractModel;
use Magento\Framework\Model\ResourceModel\Db\Context;
use Magento\Framework\Stdlib\DateTime\DateTime;
use Magelearn\Categoryfaq\Model\Question as QuestionModel;

class Question extends AbstractDb
{
    /**
     * @var string
     */
    protected $_idFieldName = 'question_id';
    /**
     * Date model
     *
     * @var DateTime
     */
    public $date;
    
    /**
     * Event Manager
     *
     * @var ManagerInterface
     */
    public $eventManager;
    
    /**
     * Question Category relation model
     *
     * @var string
     */
    public $questionCategoryTable;
    
    /**
     * @var Auth
     */
    protected $_auth;
    
    /**
     * @var RequestInterface
     */
    protected $_request;

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

            $this->questionCategoryTable = $this->getTable('magelearn_categoryfaq_question_category');
    }

    /**
     * @inheritDoc
     */
    protected function _construct()
    {
        $this->_init('magelearn_categoryfaq_question', 'question_id');
    }
    
    /**
     * @param QuestionModel|AbstractModel $object
     * @return AbstractDb
     * @throws LocalizedException
     */
    protected function _afterSave(AbstractModel $object)
    {
        $this->saveCategoryRelation($object);
        
        return parent::_afterSave($object);
    }
    
    /**
     * @param PostModel $post
     *
     * @return $this
     * @throws LocalizedException
     */
    public function saveCategoryRelation(QuestionModel $question)
    {
        $question->setIsChangedCategoryList(false);
        $id             = $question->getId();
        $categories     = $question->getCategoriesIds();
        $oldCategoryIds = $question->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, 'question_id=?' => $id];
            $adapter->delete($this->questionCategoryTable, $condition);
        }
        if (!empty($insert)) {
            $data = [];
            foreach ($insert as $categoryId) {
                $data[] = [
                    'question_id'     => (int) $id,
                    'category_id' => (int) $categoryId,
                    'position'    => 1
                ];
            }
            $adapter->insertMultiple($this->questionCategoryTable, $data);
        }
        if (!empty($insert) || !empty($delete)) {
            $categoryIds = array_unique(array_merge(array_keys($insert), array_keys($delete)));
            $this->eventManager->dispatch(
                'magelearn_categoryfaq_question_change_categories',
                ['question' => $question, 'category_ids' => $categoryIds]
                );
        }
        if (!empty($insert) || !empty($delete)) {
            $question->setIsChangedCategoryList(true);
            $categoryIds = array_keys($insert + $delete);
            $question->setAffectedCategoryIds($categoryIds);
        }
        
        return $this;
    }

    /**
     * @param QuestionModel $question
     *
     * @return array
     */
    public function getCategoryIds(QuestionModel $question)
    {
        $adapter = $this->getConnection();
        $select  = $adapter->select()->from(
            $this->questionCategoryTable,
            'category_id'
            )
            ->where(
                'question_id = ?',
                (int) $question->getId()
                );
            
        return $adapter->fetchCol($select);
    }
}

Create Model/ResourceModel/Question/Collection.php file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Model\ResourceModel\Question;

use Magento\Framework\DB\Select;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
use Magelearn\Categoryfaq\Api\Data\CategorySearchResultsInterface;
use Magelearn\Categoryfaq\Model\Question;
use Zend_Db_Select;

class Collection extends AbstractCollection
{
    /**
     * ID Field Name
     *
     * @var string
     */
    protected $_idFieldName = 'question_id';

    /**
     * @inheritDoc
     */
    protected function _construct()
    {
        $this->_init(
            Question::class,
            \Magelearn\Categoryfaq\Model\ResourceModel\Question::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 = 'question_id';
        
        return parent::_toOptionArray($valueField, $labelField, $additional); // TODO: Change the autogenerated stub
    }
    /**
     * Initialize select object
     *
     * @return Collection
     */
    protected function _initSelect()
    {
        parent::_initSelect();

        return $this;
    }
}

Create Model/ResourceModel/Question/Grid/Collection.php file.

<?php

namespace Magelearn\Categoryfaq\Model\ResourceModel\Question\Grid;

use Magento\Framework\Data\Collection\Db\FetchStrategyInterface as FetchStrategy;
use Magento\Framework\Data\Collection\EntityFactoryInterface as EntityFactory;
use Magento\Framework\Event\ManagerInterface as EventManager;
use Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult;
use Magelearn\Categoryfaq\Model\ResourceModel\Question as ResourceModel;
use Magelearn\Categoryfaq\Model\ResourceModel\Category as CategoryResourceModel;
use Psr\Log\LoggerInterface as Logger;

class Collection extends SearchResult
{
    const TABLE_NAME = 'magelearn_categoryfaq_question';
    
    /**
     * Collection constructor.
     * @param EntityFactory $entityFactory
     * @param Logger $logger
     * @param FetchStrategy $fetchStrategy
     * @param EventManager $eventManager
     * @param string $mainTable
     * @param string $resourceModel
     * @param null $identifierName
     * @param null $connectionName
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function __construct(
        EntityFactory $entityFactory,
        Logger $logger,
        FetchStrategy $fetchStrategy,
        EventManager $eventManager,
        $mainTable = self::TABLE_NAME,
        $resourceModel = ResourceModel::class,
        $identifierName = null,
        $connectionName = null
    ) {
        parent::__construct(
            $entityFactory,
            $logger,
            $fetchStrategy,
            $eventManager,
            $mainTable,
            $resourceModel,
            $identifierName,
            $connectionName
        );
    }
}

Now as per etc/di.xml file we will add our ImageUploader.

Create file at Model/ImageUploader.php

<?php

namespace Magelearn\Categoryfaq\Model;

use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Filesystem;
use Magento\Framework\Filesystem\Directory\WriteInterface;
use Magento\MediaStorage\Helper\File\Storage\Database;
use Magento\MediaStorage\Model\File\UploaderFactory;
use Magento\Store\Model\StoreManagerInterface;
use Psr\Log\LoggerInterface;

/**
 * Category FAQ image uploader
 */
class ImageUploader
{
    /**
     * Core file storage database
     *
     * @var Database
     */
    private $coreFileStorageDatabase;

    /**
     * Media directory object (writable).
     *
     * @var WriteInterface
     */
    private $mediaDirectory;

    /**
     * Uploader factory
     *
     * @var UploaderFactory
     */
    private $uploaderFactory;

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

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * Base tmp path
     *
     * @var string
     */
    public $baseTmpPath;

    /**
     * Base path
     *
     * @var string
     */
    public $basePath;

    /**
     * Allowed extensions
     *
     * @var string
     */
    public $allowedExtensions;

    /**
     * ImageUploader constructor.
     * @param Database $coreFileStorageDatabase
     * @param Filesystem $filesystem
     * @param UploaderFactory $uploaderFactory
     * @param StoreManagerInterface $storeManager
     * @param LoggerInterface $logger
     * @throws \Magento\Framework\Exception\FileSystemException
     */
    public function __construct(
        Database $coreFileStorageDatabase,
        Filesystem $filesystem,
        UploaderFactory $uploaderFactory,
        StoreManagerInterface $storeManager,
        LoggerInterface $logger
    ) {
        $this->coreFileStorageDatabase = $coreFileStorageDatabase;
        $this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
        $this->uploaderFactory = $uploaderFactory;
        $this->storeManager = $storeManager;
        $this->logger = $logger;
        $this->baseTmpPath = "faq/tmp/icon";
        $this->basePath = "faq/icon";
        $this->allowedExtensions= ['jpg', 'jpeg', 'gif', 'png'];
    }

    /**
     * Set base tmp path
     *
     * @param string $baseTmpPath
     *
     * @return void
     */
    public function setBaseTmpPath($baseTmpPath)
    {
        $this->baseTmpPath = $baseTmpPath;
    }

    /**
     * Set base path
     *
     * @param string $basePath
     *
     * @return void
     */
    public function setBasePath($basePath)
    {
        $this->basePath = $basePath;
    }

    /**
     * Set allowed extensions
     *
     * @param string[] $allowedExtensions
     *
     * @return void
     */
    public function setAllowedExtensions($allowedExtensions)
    {
        $this->allowedExtensions = $allowedExtensions;
    }

    /**
     * Retrieve base tmp path
     *
     * @return string
     */
    public function getBaseTmpPath()
    {
        return $this->baseTmpPath;
    }

    /**
     * Retrieve base path
     *
     * @return string
     */
    public function getBasePath()
    {
        return $this->basePath;
    }

    /**
     * Retrieve base path
     *
     * @return string[]
     */
    public function getAllowedExtensions()
    {
        return $this->allowedExtensions;
    }

    /**
     * Retrieve path
     *
     * @param string $path
     * @param string $imageName
     *
     * @return string
     */
    public function getFilePath($path, $imageName)
    {
        return rtrim($path, '/') . '/' . ltrim($imageName, '/');
    }

    /**
     * Checking file for moving and move it
     *
     * @param string $imageName
     *
     * @return string
     *
     * @throws LocalizedException
     */
    public function moveFileFromTmp($imageName)
    {
        $baseTmpPath = $this->getBaseTmpPath();
        $basePath = $this->getBasePath();

        $baseImagePath = $this->getFilePath($basePath, $imageName);
        $baseTmpImagePath = $this->getFilePath($baseTmpPath, $imageName);

        try {
            $this->coreFileStorageDatabase->copyFile(
                $baseTmpImagePath,
                $baseImagePath
            );
            $this->mediaDirectory->renameFile(
                $baseTmpImagePath,
                $baseImagePath
            );
        } catch (\Exception $e) {
            throw new LocalizedException(
                __('Something went wrong while saving the file(s).')
            );
        }

        return $imageName;
    }

    /**
     * Checking file for save and save it to tmp dir
     *
     * @param string $fileId
     *
     * @return string[]
     *
     * @throws LocalizedException
     */
    public function saveFileToTmpDir($fileId)
    {
        $baseTmpPath = $this->getBaseTmpPath();

        $uploader = $this->uploaderFactory->create(['fileId' => $fileId]);
        $uploader->setAllowedExtensions($this->getAllowedExtensions());
        $uploader->setAllowRenameFiles(true);

        $result = $uploader->save($this->mediaDirectory->getAbsolutePath($baseTmpPath));

        if (!$result) {
            throw new LocalizedException(
                __('File can not be saved to the destination folder.')
            );
        }

        /**
         * Workaround for prototype 1.7 methods "isJSON", "evalJSON" on Windows OS
         */
        $result['tmp_name'] = str_replace('\\', '/', $result['tmp_name']);
        $result['path'] = str_replace('\\', '/', $result['path']);
        $result['url'] = $this->storeManager
                ->getStore()
                ->getBaseUrl(
                    \Magento\Framework\UrlInterface::URL_TYPE_MEDIA
                ) . $this->getFilePath($baseTmpPath, $result['file']);
        $result['name'] = $result['file'];

        if (isset($result['file'])) {
            try {
                $relativePath = rtrim($baseTmpPath, '/') . '/' . ltrim($result['file'], '/');
                $this->coreFileStorageDatabase->saveFile($relativePath);
            } catch (\Exception $e) {
                $this->logger->critical($e);
                throw new LocalizedException(
                    __('Something went wrong while saving the file(s).')
                );
            }
        }

        return $result;
    }
}

Create file at Controller/Adminhtml/Category/Upload.php file.

<?php

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Category;

use Magento\Backend\App\Action;
use Magento\Framework\Registry;
use Magelearn\Categoryfaq\Model\CategoryFactory;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Controller\ResultInterface;
use Magelearn\Categoryfaq\Model\ImageUploader;

class Upload extends \Magelearn\Categoryfaq\Controller\Adminhtml\Category
{
    /**
     * Image uploader
     *
     * @var ImageUploader
     */
    public $imageUploader;
    
    /**
     * Upload constructor.
     *
     * @param Action\Context $context
     * @param \Magento\Framework\Registry $coreRegistry
     * @param CategoryFactory $categoryFactory
     * @param ImageUploader $imageUploader
     */
    public function __construct(
        Action\Context $context,
        Registry $coreRegistry,
        CategoryFactory $categoryFactory,
        ImageUploader $imageUploader
    ) {
        parent::__construct($context, $coreRegistry, $categoryFactory);
        $this->imageUploader = $imageUploader;
    }

    /**
     * Upload file controller action
     *
     * @return ResultInterface
     */
    public function execute()
    {
        try {
            $result = $this->imageUploader->saveFileToTmpDir('icon');

            $result['cookie'] = [
                'name' => $this->_getSession()->getName(),
                'value' => $this->_getSession()->getSessionId(),
                'lifetime' => $this->_getSession()->getCookieLifetime(),
                'path' => $this->_getSession()->getCookiePath(),
                'domain' => $this->_getSession()->getCookieDomain(),
            ];
        } catch (\Exception $e) {
            $result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()];
        }
        return $this->resultFactory->create(ResultFactory::TYPE_JSON)->setData($result);
    }
}

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

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Controller\Adminhtml;

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

abstract class Category extends Action
{

    const ADMIN_RESOURCE = 'Magelearn_Categoryfaq::management';
    
    /**
     * Category Factory
     *
     * @var CategoryFactory
     */
    public $categoryFactory;
    
    protected $_coreRegistry;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\Registry $coreRegistry
     * @param CategoryFactory $categoryFactory
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\Registry $coreRegistry,
        CategoryFactory $categoryFactory
    ) {
        $this->categoryFactory = $categoryFactory;
        $this->_coreRegistry = $coreRegistry;
        parent::__construct($context);
    }

    /**
     * Init page
     *
     * @param \Magento\Backend\Model\View\Result\Page $resultPage
     * @return \Magento\Backend\Model\View\Result\Page
     */
    public function initPage($resultPage)
    {
        $resultPage->setActiveMenu(self::ADMIN_RESOURCE)
            ->addBreadcrumb(__('Magelearn'), __('Magelearn'))
            ->addBreadcrumb(__('Category'), __('Category'));
        return $resultPage;
    }
    
    /**
     * @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\Categoryfaq\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_categoryfaq_category', $category);
        }
        
        return $category;
    }
}

Now as defined in etc/adminhtml/menu.xml file, we will start to create action URLs for question and category menus.

For that first, add view/adminhtml/layout/magelearn_categoryfaq_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"/>
	<head>
        <title>FAQ Categories</title>
    </head>
	<body>
		<referenceContainer name="content">
			<uiComponent name="magelearn_categoryfaq_category_listing"/>
		</referenceContainer>
	</body>
</page>

Also add Controller/Adminhtml/Category/Index.php file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Category;

class Index extends \Magento\Backend\App\Action
{

    protected $resultPageFactory;

    /**
     * Constructor
     *
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
        $this->resultPageFactory = $resultPageFactory;
        parent::__construct($context);
    }

    /**
     * Index action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        $resultPage = $this->resultPageFactory->create();
        $resultPage->setActiveMenu('Magelearn_Categoryfaq::magelearn_categoryfaq_category');
        $resultPage->getConfig()->getTitle()->prepend(__("FAQ Categories"));
        return $resultPage;
    }
}

Now as per the highlighted code in view/adminhtml/layout/magelearn_categoryfaq_category_index.xml file, we will add uiComponent file at view/adminhtml/ui_component/magelearn_categoryfaq_category_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_categoryfaq_category_listing.magelearn_categoryfaq_category_listing_data_source</item>
		</item>
	</argument>
	<settings>
		<spinner>magelearn_categoryfaq_category_columns</spinner>
		<deps>
			<dep>magelearn_categoryfaq_category_listing.magelearn_categoryfaq_category_listing_data_source</dep>
		</deps>
		<buttons>
			<button name="add">
				<url path="*/*/new"/>
				<class>primary</class>
				<label translate="true">Add new category</label>
			</button>
		</buttons>
	</settings>
	<dataSource name="magelearn_categoryfaq_category_listing_data_source" component="Magento_Ui/js/grid/provider">
		<settings>
			<storageConfig>
				<param name="indexField" xsi:type="string">category_id</param>
			</storageConfig>
			<updateUrl path="mui/index/render"/>
		</settings>
		<aclResource>Magelearn_Categoryfaq::category</aclResource>
		<dataProvider name="magelearn_categoryfaq_category_listing_data_source" class="Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider">
			<settings>
				<requestFieldName>category_id</requestFieldName>
				<primaryFieldName>category_id</primaryFieldName>
			</settings>
		</dataProvider>
	</dataSource>
	<listingToolbar name="listing_top">
		<settings>
			<sticky>true</sticky>
		</settings>
		<bookmark name="bookmarks"/>
		<columnsControls name="columns_controls"/>
		<filterSearch name="fulltext"/>
		<filters name="listing_filters"/>
		<paging name="listing_paging"/>
		<exportButton name="export_button"/>
		<massaction name="listing_massaction" component="Magento_Ui/js/grid/tree-massactions">
            <action name="delete">
                <settings>
                    <confirm>
                        <message translate="true">Are you sure to delete selected FAQ categories?</message>
                        <title translate="true">Delete items</title>
                    </confirm>
                    <url path="magelearn_categoryfaq/category/massDelete"/>
                    <type>delete</type>
                    <label translate="true">Delete</label>
                </settings>
            </action>
            <action name="massEnable">
                <settings>
                    <url path="magelearn_categoryfaq/category/massEnable"/>
                    <type>enable</type>
                    <label translate="true">Enable</label>
                </settings>
            </action>
            <action name="massDisable">
                <settings>
                    <url path="magelearn_categoryfaq/category/massDisable"/>
                    <type>disable</type>
                    <label translate="true">Disable</label>
                </settings>
            </action>
        </massaction>
	</listingToolbar>
	<columns name="magelearn_categoryfaq_category_columns">
		<settings>
			<editorConfig>
				<param name="selectProvider" xsi:type="string">magelearn_categoryfaq_category_listing.magelearn_categoryfaq_category_listing.magelearn_categoryfaq_category_columns.ids</param>
				<param name="enabled" xsi:type="boolean">true</param>
				<param name="indexField" xsi:type="string">category_id</param>
				<param name="clientConfig" xsi:type="array">
					<item name="saveUrl" xsi:type="url" path="magelearn_categoryfaq/category/inlineEdit"/>
					<item name="validateBeforeSave" xsi:type="boolean">false</item>
				</param>
			</editorConfig>
			<childDefaults>
				<param name="fieldAction" xsi:type="array">
					<item name="provider" xsi:type="string">magelearn_categoryfaq_category_listing.magelearn_categoryfaq_category_listing.magelearn_categoryfaq_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>
				</param>
			</childDefaults>
		</settings>
		<selectionsColumn name="ids">
			<settings>
				<indexField>category_id</indexField>
			</settings>
		</selectionsColumn>
		<column name="category_id">
			<settings>
				<filter>text</filter>
				<label translate="true">id</label>
				<editor>
					<editorType>text</editorType>
					<validation>
						<rule name="required-entry" xsi:type="boolean">false</rule>
					</validation>
				</editor>
			</settings>
		</column>
		<column name="name">
			<settings>
				<filter>text</filter>
				<label translate="true">Name</label>
				<editor>
					<editorType>text</editorType>
					<validation>
						<rule name="required-entry" xsi:type="boolean">false</rule>
					</validation>
				</editor>
			</settings>
		</column>
		<column name="description">
			<settings>
				<filter>text</filter>
				<label translate="true">Description</label>
				<editor>
					<editorType>text</editorType>
					<validation>
						<rule name="required-entry" xsi:type="boolean">false</rule>
					</validation>
				</editor>
			</settings>
		</column>
		<column name="status">
			<argument name="data" xsi:type="array">
                <item name="options" xsi:type="object">Magelearn\Categoryfaq\Model\Category\Status\Options</item>
                <item name="config" xsi:type="array">
                	<item name="filter" xsi:type="string">select</item>
                    <item name="component" xsi:type="string">Magelearn_Categoryfaq/js/grid/columns/status</item>
                    <item name="dataType" xsi:type="string">select</item>
                    <item name="editor" xsi:type="string">select</item>
                    <item name="sortOrder" xsi:type="number">40</item>
                    <item name="sorting" xsi:type="string">asc</item>
                    <item name="label" xsi:type="string" translate="true">Status</item>
                </item>
            </argument>
		</column>
		<column name="sort_order">
			<settings>
				<filter>text</filter>
				<label translate="true">Sort Order</label>
				<editor>
					<editorType>text</editorType>
					<validation>
						<rule name="required-entry" xsi:type="boolean">true</rule>
						<rule name="required-entry" xsi:type="boolean">true</rule>
					</validation>
				</editor>
			</settings>
		</column>
		<column name="created_at" class="Magento\Ui\Component\Listing\Columns\Date">
			<settings>
				<filter>text</filter>
				<label translate="true">Created</label>
				<editor>
					<editorType>text</editorType>
					<validation>
						<rule name="required-entry" xsi:type="boolean">false</rule>
					</validation>
				</editor>
			</settings>
		</column>
		<column name="updated_at" class="Magento\Ui\Component\Listing\Columns\Date">
			<settings>
				<filter>text</filter>
				<label translate="true">Modified</label>
				<editor>
					<editorType>text</editorType>
					<validation>
						<rule name="required-entry" xsi:type="boolean">false</rule>
					</validation>
				</editor>
			</settings>
		</column>
		<column name="icon" class="Magelearn\Categoryfaq\Ui\Component\Listing\Column\CategoryIcon">
			<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">Icon</item>
				</item>
			</argument>
		</column>
		<actionsColumn name="actions" class="Magelearn\Categoryfaq\Ui\Component\Listing\Column\CategoryActions">
			<settings>
				<indexField>category_id</indexField>
				<resizeEnabled>false</resizeEnabled>
				<resizeDefaultWidth>107</resizeDefaultWidth>
			</settings>
		</actionsColumn>
	</columns>
</listing>

Now as highlighted in the above code, we will start to create different files.

Add Model/Category/Status/Options.php file.

<?php

namespace Magelearn\Categoryfaq\Model\Category\Status;

class Options implements \Magento\Framework\Data\OptionSourceInterface
{
    /**
     * Grid display options.
     *
     * @return array
     */
    public function toOptionArray()
    {
        return [
            ['value' => '0', 'label' => __('Disabled')],
            ['value' => '1', 'label' => __('Enabled')]
        ];
    }
}

Add view/adminhtml/web/js/grid/columns/status.js file.

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.status) {
                    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;
                }
            }
            return label;
        }
    });
});

Add Ui/Component/Listing/Column/CategoryIcon.php file.

<?php

namespace Magelearn\Categoryfaq\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;

class CategoryIcon extends Column
{
    private $storeManager;

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

    /**
     * @var UrlInterface
     */
    private $_backendUrl;

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

    /**
     * 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
            ) . 'faq/tmp/icon/';
            $baseImage = $this->assetRepo->getUrl('Magelearn_Categoryfaq::images/faq.png');
            $fieldName = $this->getData('name');
            foreach ($dataSource['data']['items'] as & $item) {
                if ($item[$fieldName]) {
                    $item[$fieldName . '_src'] = $path . $item['icon'];
                    $item[$fieldName . '_alt'] = $item['name'];
                    $item[$fieldName . '_orig_src'] = $path . $item['icon'];
                } else {
                    $item[$fieldName . '_src'] = $baseImage;
                    $item[$fieldName . '_alt'] = 'Category Icon';
                    $item[$fieldName . '_orig_src'] = $baseImage;
                }
                $item[$fieldName . '_link'] = $this->_backendUrl->getUrl(
                    "magelearn_categoryfaq/category/edit",
                    ['category_id' => $item['category_id']]
                );
            }
        }

        return $dataSource;
    }
}

Add the Image file at view/adminhtml/web/images/faq.png

Add Ui/Component/Listing/Column/CategoryActions.php file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

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

class CategoryActions extends \Magento\Ui\Component\Listing\Columns\Column
{

    const URL_PATH_DETAILS = 'magelearn_categoryfaq/category/details';
    protected $urlBuilder;
    const URL_PATH_EDIT = 'magelearn_categoryfaq/category/edit';
    const URL_PATH_DELETE = 'magelearn_categoryfaq/category/delete';

    /**
     * @param \Magento\Framework\View\Element\UiComponent\ContextInterface $context
     * @param \Magento\Framework\View\Element\UiComponentFactory $uiComponentFactory
     * @param \Magento\Framework\UrlInterface $urlBuilder
     * @param array $components
     * @param array $data
     */
    public function __construct(
        \Magento\Framework\View\Element\UiComponent\ContextInterface $context,
        \Magento\Framework\View\Element\UiComponentFactory $uiComponentFactory,
        \Magento\Framework\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) {
                if (isset($item['category_id'])) {
                    $item[$this->getData('name')] = [
                        'edit' => [
                            'href' => $this->urlBuilder->getUrl(
                                static::URL_PATH_EDIT,
                                [
                                    'category_id' => $item['category_id']
                                ]
                            ),
                            'label' => __('Edit')
                        ]
                    ];
                }
            }
        }
        
        return $dataSource;
    }
}

Now we will add our mass actions file.

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

<?php

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Category;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\ResultFactory;
use Magento\Ui\Component\MassAction\Filter;
use Magelearn\Categoryfaq\Model\ResourceModel\Category\CollectionFactory;
use Magento\Framework\App\Action\HttpPostActionInterface;

/**
 * Class MassDelete
 * @package Magelearn\Categoryfaq\Controller\Adminhtml\Category
 */
class MassDelete extends Action implements HttpPostActionInterface
{
    /**
     * @var Filter
     */
    protected $filter;

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

    /**
     * @param Context $context
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(Context $context, Filter $filter, CollectionFactory $collectionFactory)
    {
        parent::__construct($context);

        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;
    }

    /**
     * Execute Mass Delete Action
     *
     * @return \Magento\Backend\Model\View\Result\Redirect
     * @throws \Magento\Framework\Exception\LocalizedException|\Exception
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());
        $collectionSize = $collection->getSize();
        foreach ($collection as $item) {
            $item->delete();
        }

        $this->messageManager->addSuccessMessage(__('A total of %1 categories have been deleted.', $collectionSize));

        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
        return $resultRedirect->setPath('*/*/');
    }
}

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

<?php

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Category;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\ResultFactory;
use Magento\Ui\Component\MassAction\Filter;
use Magelearn\Categoryfaq\Model\ResourceModel\Category\CollectionFactory;
use Magento\Framework\App\Action\HttpPostActionInterface;

/**
 * Class MassDisable
 * @package Magelearn\Categoryfaq\Controller\Adminhtml\Category
 */
class MassDisable extends Action implements HttpPostActionInterface
{
    /**
     * @var Filter
     */
    protected $filter;

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

    /**
     * @param Context $context
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(Context $context, Filter $filter, CollectionFactory $collectionFactory)
    {
        parent::__construct($context);

        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;
    }

    /**
     * Execute Mass Disable Action
     *
     * @return \Magento\Backend\Model\View\Result\Redirect
     * @throws \Magento\Framework\Exception\LocalizedException|\Exception
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());
        $collectionSize = $collection->getSize();

        foreach ($collection as $item) {
            $item->setStatus(0);
            $item->save();
        }

        $this->messageManager->addSuccessMessage(__('A total of %1 categories have been disabled.', $collectionSize));

        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
        return $resultRedirect->setPath('*/*/');
    }
}

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

<?php

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Category;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\ResultFactory;
use Magento\Ui\Component\MassAction\Filter;
use Magelearn\Categoryfaq\Model\ResourceModel\Category\CollectionFactory;
use Magento\Framework\App\Action\HttpPostActionInterface;

/**
 * Class MassEnable
 * @package Magelearn\Categoryfaq\Controller\Adminhtml\Category
 */
class MassEnable extends Action implements HttpPostActionInterface
{
    /**
     * @var Filter
     */
    protected $filter;

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

    /**
     * @param Context $context
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(Context $context, Filter $filter, CollectionFactory $collectionFactory)
    {
        parent::__construct($context);

        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;
    }

    /**
     * Execute action
     *
     * @return \Magento\Backend\Model\View\Result\Redirect
     * @throws \Magento\Framework\Exception\LocalizedException|\Exception
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());
        $collectionSize = $collection->getSize();

        foreach ($collection as $item) {
            $item->setStatus(1);
            $item->save();
        }

        $this->messageManager->addSuccessMessage(__('A total of %1 categories have been enabled.', $collectionSize));

        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);

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

Now for category inline edit, Add a file at Controller/Adminhtml/Category/InlineEdit.php

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Category;

class InlineEdit extends \Magento\Backend\App\Action
{

    protected $jsonFactory;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\Controller\Result\JsonFactory $jsonFactory
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\Controller\Result\JsonFactory $jsonFactory
    ) {
        parent::__construct($context);
        $this->jsonFactory = $jsonFactory;
    }

    /**
     * Inline edit action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        /** @var \Magento\Framework\Controller\Result\Json $resultJson */
        $resultJson = $this->jsonFactory->create();
        $error = false;
        $messages = [];
        
        if ($this->getRequest()->getParam('isAjax')) {
            $postItems = $this->getRequest()->getParam('items', []);
            if (!count($postItems)) {
                $messages[] = __('Please correct the data sent.');
                $error = true;
            } else {
                foreach (array_keys($postItems) as $modelid) {
                    /** @var \Magelearn\Categoryfaq\Model\Category $model */
                    $model = $this->_objectManager->create(\Magelearn\Categoryfaq\Model\Category::class)->load($modelid);
                    try {
                        $model->setData(array_merge($model->getData(), $postItems[$modelid]));
                        $model->save();
                    } catch (\Exception $e) {
                        $messages[] = "[Category ID: {$modelid}]  {$e->getMessage()}";
                        $error = true;
                    }
                }
            }
        }
        
        return $resultJson->setData([
            'messages' => $messages,
            'error' => $error
        ]);
    }
}

Now to create a new category add a file at Controller/Adminhtml/Category/NewAction.php file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Category;
use Magelearn\Categoryfaq\Model\CategoryFactory;

class NewAction extends \Magelearn\Categoryfaq\Controller\Adminhtml\Category
{

    protected $resultForwardFactory;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\Registry $coreRegistry
     * @param \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory
     * @param CategoryFactory $categoryFactory
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\Registry $coreRegistry,
        \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory,
        CategoryFactory $categoryFactory
    ) {
        $this->resultForwardFactory = $resultForwardFactory;
        parent::__construct($context, $coreRegistry, $categoryFactory);
    }

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

Also, add layout file at view/adminhtml/layout/magelearn_categoryfaq_category_new.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="magelearn_categoryfaq_category_edit"/>
</page>

As per the highlighted code, this new action is redirected to the edit action.

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

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Category;
use Magelearn\Categoryfaq\Model\CategoryFactory;

class Edit extends \Magelearn\Categoryfaq\Controller\Adminhtml\Category
{

    protected $resultPageFactory;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\Registry $coreRegistry
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     * @param CategoryFactory $categoryFactory
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\Registry $coreRegistry,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory,
        CategoryFactory $categoryFactory
    ) {
        $this->resultPageFactory = $resultPageFactory;
        parent::__construct($context, $coreRegistry, $categoryFactory);
    }

    /**
     * Edit action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        // 1. Get ID and create model
        $id = $this->getRequest()->getParam('category_id');
        $model = $this->_objectManager->create(\Magelearn\Categoryfaq\Model\Category::class);
        
        // 2. Initial checking
        if ($id) {
            $model->load($id);
            if (!$model->getId()) {
                $this->messageManager->addErrorMessage(__('This Category no longer exists.'));
                /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
                $resultRedirect = $this->resultRedirectFactory->create();
                return $resultRedirect->setPath('*/*/');
            }
        }
        $this->_coreRegistry->register('magelearn_categoryfaq_category', $model);
        
        // 3. Build edit form
        /** @var \Magento\Backend\Model\View\Result\Page $resultPage */
        $resultPage = $this->resultPageFactory->create();
        $this->initPage($resultPage)->addBreadcrumb(
            $id ? __('Edit Category') : __('New Category'),
            $id ? __('Edit Category') : __('New Category')
        );
        $resultPage->getConfig()->getTitle()->prepend(__('Categorys'));
        $resultPage->getConfig()->getTitle()->prepend($model->getId() ? __('Edit Category %1', $model->getId()) : __('New Category'));
        return $resultPage;
    }
    public function _isAllowed()
    {
        return $this->_authorization->isAllowed('Magelearn_Categoryfaq::edit');
    }
}

Also, add a layout file at view/adminhtml/layout/magelearn_categoryfaq_category_edit.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>
        <referenceContainer name="content">
            <uiComponent name="magelearn_categoryfaq_category_form"/>
        </referenceContainer>
    </body>
</page>

Now as per the highlighted code above, we will add a UI Component file at view/adminhtml/ui_component/magelearn_categoryfaq_category_form.xml

<?xml version="1.0" ?>
<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">magelearn_categoryfaq_category_form.category_form_data_source</item>
        </item>
        <item name="label" xsi:type="string" translate="true">General Information</item>
        <item name="template" xsi:type="string">templates/form/collapsible</item>
    </argument>
    <settings>
        <buttons>
            <button name="back" class="Magelearn\Categoryfaq\Block\Adminhtml\Category\Edit\BackButton"/>
            <button name="delete" class="Magelearn\Categoryfaq\Block\Adminhtml\Category\Edit\DeleteButton"/>
            <button name="save" class="Magelearn\Categoryfaq\Block\Adminhtml\Category\Edit\SaveButton"/>
            <button name="save_and_continue" class="Magelearn\Categoryfaq\Block\Adminhtml\Category\Edit\SaveAndContinueButton"/>
        </buttons>
        <namespace>magelearn_categoryfaq_category_form</namespace>
        <dataScope>data</dataScope>
        <deps>
            <dep>magelearn_categoryfaq_category_form.category_form_data_source</dep>
        </deps>
    </settings>
    <dataSource name="category_form_data_source">
        <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>
        <settings>
            <submitUrl path="*/*/save"/>
        </settings>
        <dataProvider name="category_form_data_source" class="Magelearn\Categoryfaq\Model\Category\DataProvider">
            <settings>
                <requestFieldName>category_id</requestFieldName>
                <primaryFieldName>category_id</primaryFieldName>
            </settings>
        </dataProvider>
    </dataSource>
    <fieldset name="general">
        <settings>
            <label>General</label>
        </settings>
        <field name="name" formElement="input" sortOrder="20">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">category</item>
                </item>
            </argument>
            <settings>
                <dataType>string</dataType>
                <label translate="true">Name</label>
                <dataScope>name</dataScope>
                <validation>
                    <rule name="required-entry" xsi:type="boolean">true</rule>
                </validation>
            </settings>
        </field>
        <field name="description" formElement="textarea" sortOrder="30">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">category</item>
                </item>
            </argument>
            <settings>
                <dataType>text</dataType>
                <label translate="true">Description</label>
                <dataScope>description</dataScope>
                <validation>
                    <rule name="required-entry" xsi:type="boolean">false</rule>
                </validation>
            </settings>
        </field>
        <field name="sort_order" formElement="input" sortOrder="40">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">category</item>
                </item>
            </argument>
            <settings>
                <dataType>text</dataType>
                <label translate="true">Sort Order</label>
                <dataScope>sort_order</dataScope>
                <validation>
                    <rule name="required-entry" xsi:type="boolean">true</rule>
                    <rule name="integer" xsi:type="boolean">true</rule>
                </validation>
            </settings>
        </field>
        <field name="icon" sortOrder="50">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="dataType" xsi:type="string">string</item>
                    <item name="source" xsi:type="string">category</item>
                    <item name="label" xsi:type="string" translate="true">Category Icon</item>
                    <item name="visible" xsi:type="boolean">true</item>
                    <item name="formElement" xsi:type="string">fileUploader</item>
                    <item name="elementTmpl" xsi:type="string">ui/form/element/uploader/uploader</item>
                    <item name="previewTmpl" xsi:type="string">Magelearn_Categoryfaq/image-preview</item>
                    <item name="required" xsi:type="boolean">false</item>
                    <item name="sortOrder" xsi:type="number">50</item>
                    <item name="uploaderConfig" xsi:type="array">
                        <item name="url" xsi:type="url" path="magelearn_categoryfaq/category/upload"/>
                    </item>
                </item>
            </argument>
        </field>
        <field name="status" formElement="checkbox" sortOrder="60">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">category</item>
                    <item name="prefer" xsi:type="string">toggle</item>
                    <item name="valueMap" xsi:type="array">
                        <item name="true" xsi:type="string">1</item>
                        <item name="false" xsi:type="string">0</item>
                    </item>
                    <item name="default" xsi:type="string">1</item>                    
                </item>
            </argument>
            <settings>
                <dataType>boolean</dataType>
                <label translate="true">Status</label>
                <dataScope>status</dataScope>
                <validation>
                    <rule name="required-entry" xsi:type="boolean">false</rule>
                </validation>
            </settings>
        </field>
    </fieldset>
</form>

Now as per the highlighted code above, we will add our DataProvider class.

Add file at Model/Category/DataProvider.php

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Model\Category;

use Magelearn\Categoryfaq\Model\ResourceModel\Category\CollectionFactory;
use Magento\Framework\App\Request\DataPersistorInterface;
use Magento\Ui\DataProvider\AbstractDataProvider;
use Magento\Framework\Filesystem;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Store\Model\StoreManagerInterface;

class DataProvider extends AbstractDataProvider
{

    /**
     * @var DataPersistorInterface
     */
    protected $dataPersistor;

    /**
     * @var array
     */
    protected $loadedData;
    /**
     * @inheritDoc
     */
    protected $collection;
    protected $filesystem;
    /**
     * @var StoreManagerInterface
     */
    protected $storeManager;


    /**
     * @param string $name
     * @param string $primaryFieldName
     * @param string $requestFieldName
     * @param CollectionFactory $collectionFactory
     * @param DataPersistorInterface $dataPersistor
     * @param StoreManagerInterface $storeManager
     * @param array $meta
     * @param array $data
     */
    public function __construct(
        $name,
        $primaryFieldName,
        $requestFieldName,
        CollectionFactory $collectionFactory,
        DataPersistorInterface $dataPersistor,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        Filesystem $filesystem,
        array $meta = [],
        array $data = []
    ) {
        $this->collection = $collectionFactory->create();
        $this->dataPersistor = $dataPersistor;
        $this->filesystem = $filesystem;
        $this->storeManager = $storeManager;
        parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
    }

    /**
     * @inheritDoc
     */
    public function getData()
    {
        if (isset($this->loadedData)) {
            return $this->loadedData;
        }
        $items = $this->collection->getItems();
        foreach ($items as $model) {
            $this->loadedData[$model->getCategoryId()] = $model->getData();
            if ($model->getIcon()) {
                $m['icon'][0]['name'] = $model->getIcon();
                $m['icon'][0]['url'] = $this->getMediaUrl() . $model->getIcon();
                $fullData = $this->loadedData;
                $this->loadedData[$model->getCategoryId()] = array_merge($fullData[$model->getCategoryId()], $m);
            }
        }
        $data = $this->dataPersistor->get('magelearn_categoryfaq_category');
        
        if (!empty($data)) {
            $model = $this->collection->getNewEmptyItem();
            $model->setData($data);
            $this->loadedData[$model->getCategoryId()] = $model->getData();
            $this->dataPersistor->clear('magelearn_categoryfaq_category');
        }
        
        return $this->loadedData;
    }
    /**
     * Get media url
     *
     * @return string
     * @throws NoSuchEntityException
     */
    public function getMediaUrl()
    {
        $mediaUrl = $this->storeManager->getStore()
        ->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA) . 'faq/tmp/icon/';
        return $mediaUrl;
    }
}

As per defined in submitUrl node we will add file at Controller/Adminhtml/Category/Save.php

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Category;

use Magento\Framework\Registry;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Message\MessageInterface;
use Magento\Framework\View\Element\Messages;
use Magento\Framework\View\LayoutFactory;
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 Magelearn\Categoryfaq\Model\CategoryFactory;
use Magelearn\Categoryfaq\Model\ImageUploader;

class Save extends \Magelearn\Categoryfaq\Controller\Adminhtml\Category
{
    /**
     * Result Raw Factory
     *
     * @var RawFactory
     */
    public $resultRawFactory;
    
    /**
     * Result Json Factory
     *
     * @var JsonFactory
     */
    public $resultJsonFactory;
    
    /**
     * Layout Factory
     *
     * @var LayoutFactory
     */
    public $layoutFactory;
    
    /**
     * DataProcessor
     *
     * @var PostDataProcessor
     */
    protected $dataProcessor;
    
    /**
     * Category Factory
     *
     * @var CategoryFactory
     */
    public $categoryFactory;
    
    protected $dataPersistor;
    
    /**
     * @var \Magento\Framework\Stdlib\DateTime\DateTime
     */
    protected $date;
    
    /**
     * @var ImageUploader
     */
    protected $imageUploader;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param Registry $coreRegistry
     * @param \Magento\Framework\App\Request\DataPersistorInterface $dataPersistor
     * @param RawFactory $resultRawFactory
     * @param JsonFactory $resultJsonFactory
     * @param LayoutFactory $layoutFactory
     * @param PostDataProcessor $dataProcessor,
     * @param CategoryFactory $categoryFactory,
     * @param \Magento\Framework\Stdlib\DateTime\DateTime $date
     * @param ImageUploader $imageUploader
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        Registry $coreRegistry,
        \Magento\Framework\App\Request\DataPersistorInterface $dataPersistor,
        RawFactory $resultRawFactory,
        JsonFactory $resultJsonFactory,
        LayoutFactory $layoutFactory,
        PostDataProcessor $dataProcessor,
        CategoryFactory $categoryFactory,
        \Magento\Framework\Stdlib\DateTime\DateTime $date,
        ImageUploader $imageUploader
    ) {
        $this->resultRawFactory = $resultRawFactory;
        $this->resultJsonFactory = $resultJsonFactory;
        $this->layoutFactory = $layoutFactory;
        $this->dataProcessor = $dataProcessor;
        $this->dataPersistor = $dataPersistor;
        $this->date = $date;
        $this->imageUploader = $imageUploader;
        parent::__construct($context, $coreRegistry, $categoryFactory);
    }

    /**
     * Save action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        if ($this->getRequest()->getPost('return_session_messages_only')) {
            $category = $this->initCategory();
            $categoryPostData = $this->getRequest()->getPostValue();
            $categoryPostData['status'] = 1;
            
            $category->addData($categoryPostData);

            try {
                if ($this->dataProcessor->validate($categoryPostData, true)) {
                    $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' => 1,
                '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()
                ]
                );
        }
        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultRedirectFactory->create();
        $data = $this->getRequest()->getPostValue();

        if ($data) {
            if (!$this->dataProcessor->validate($data, false)) {
                $this->dataPersistor->set('magelearn_categoryfaq_category', $data);
                if (!empty($data['category_id'])) {
                    return $resultRedirect->setPath(
                        '*/*/edit',
                        [
                            'id' => $data['category_id'],
                            '_current' => true
                        ]
                        );
                } else {
                    return $resultRedirect->setPath(
                        '*/*/edit',
                        [
                            '_current' => true
                        ]
                        );
                }
            }
            
            $id = $this->getRequest()->getParam('category_id');
            
            $category = $this->categoryFactory->create();
            $model = $category->load($id);
            if (!$model->getId() && $id) {
                $this->messageManager->addErrorMessage(__('This Category no longer exists.'));
                return $resultRedirect->setPath('*/*/');
            }
            
            $data['updated_at'] =  $this->date->gmtDate();
            if (isset($data['icon'][0]['name'])) {
                $data['icon'] = $data['icon'][0]['name'];
            } else {
                $data['icon'] = null;
            }
            $model->setData($data);
        
            try {
                $model->save();
                $this->messageManager->addSuccessMessage(__('You saved the Category.'));
                $this->dataPersistor->clear('magelearn_categoryfaq_category');
        
                if ($this->getRequest()->getParam('back')) {
                    return $resultRedirect->setPath('*/*/edit', ['category_id' => $model->getId()]);
                }
                return $resultRedirect->setPath('*/*/');
            } catch (LocalizedException $e) {
                $this->messageManager->addErrorMessage($e->getMessage());
            } catch (\Exception $e) {
                $this->messageManager->addExceptionMessage($e, __('Something went wrong while saving the Category.'));
            }
        
            $this->dataPersistor->set('magelearn_categoryfaq_category', $data);
            return $resultRedirect->setPath('*/*/edit', ['category_id' => $this->getRequest()->getParam('category_id')]);
        }
        return $resultRedirect->setPath('*/*/');
    }
}

Now as per the highlighted code above, to validate the category data properly, we will add Controller/Adminhtml/Category/PostDataProcessor.php file.

<?php

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Category;

use Magelearn\Categoryfaq\Model\CategoryFactory;
use Magelearn\Categoryfaq\Model\ResourceModel\Category\CollectionFactory;
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;

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

    /**
     * PostDataProcessor Class constructor
     *
     * @param Date               $dateFilter         DateFiletr
     * @param ManagerInterface   $messageManager     MessageManager
     * @param ValidatorFactory   $validatorFactory   ValidationFactory
     * @param CollectionFactory  $collectionFactory  CollectionFactory
     */
    public function __construct(
        Date $dateFilter,
        ManagerInterface $messageManager,
        ValidatorFactory $validatorFactory,
        CollectionFactory $collectionFactory
    ) {
        $this->dateFilter = $dateFilter;
        $this->messageManager = $messageManager;
        $this->validatorFactory = $validatorFactory;
        $this->collectionFactory = $collectionFactory;
    }

    /**
     * Validate post data
     *
     * @param array $data Datapost
     *
     * @return bool
     */
    public function validate($data, $sessionData = false)
    {
        $errorNo1 = $this->validateRequireEntry($data, $sessionData);
        $errorNo2 = $this->checkNameExist($data);
        $errorNo3 = true;

        if (!in_array($data['status'], [0,1]) || $data['status'] == '' || $data['status'] === 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, $sessionData)
    {
        if($sessionData) {
            $requiredFields = [
                'name' => __('Category Name')
            ];
        } else {
            $requiredFields = [
                'name' => __('Category Name'),
                'sort_order' => __('Sort Order')
            ];
        }

        $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)
    {
        $errorNo = true;
        if (isset($data['category_id'])) {
            $categoryCollection = $this->collectionFactory->create()
                ->addFieldToFilter('category_id', ['neq' => $data['category_id']]);
        } else {
            $categoryCollection = $this->collectionFactory->create();
        }
        foreach ($categoryCollection as $category) {
            $categoryName = trim(mb_strtolower(preg_replace('/\s+/', ' ', $category->getName()), 'UTF-8'));
            if (trim(preg_replace('/\s+/', ' ', mb_strtolower($data['name'], 'UTF-8'))) == $categoryName) {
                $errorNo = false;
                $this->messageManager->addErrorMessage(
                    __('This name is already exist.')
                );
            }
        }
        return $errorNo;
    }
}

Now to display the category Icon preview image properly in Edit Category form, we will add a file at view/adminhtml/web/template/image-preview.html

<div class="file-uploader-summary">
    <div class="file-uploader-preview">
        <a attr="href: $parent.getFilePreview($file)" target="_blank">
            <img
                class="preview-image"
                tabindex="0"
                event="load: $parent.onPreviewLoad.bind($parent)"
                attr="
                    src: $parent.getFilePreview($file),
                    alt: $file.name">
        </a>

        <div class="actions">
            <button
                type="button"
                class="action-remove"
                data-role="delete-button"
                attr="title: $t('Delete image')"
                click="$parent.removeFile.bind($parent, $file)">
                <span translate="'Delete image'"/>
            </button>
        </div>
    </div>

    <div class="file-uploader-filename" text="$file.name"/>
    <div class="file-uploader-meta">
        <text args="$file.previewWidth"/>x<text args="$file.previewHeight"/>
    </div>
</div>

Now we will add files to display different buttons on the edit form.

Add file at Block/Adminhtml/Category/Edit/BackButton.php

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

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

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

class BackButton extends GenericButton implements ButtonProviderInterface
{

    /**
     * @return array
     */
    public function getButtonData()
    {
        return [
            'label' => __('Back'),
            'on_click' => sprintf("location.href = '%s';", $this->getBackUrl()),
            'class' => 'back',
            'sort_order' => 10
        ];
    }

    /**
     * Get URL for back (reset) button
     *
     * @return string
     */
    public function getBackUrl()
    {
        return $this->getUrl('*/*/');
    }
}

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

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

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

use Magento\Backend\Block\Widget\Context;

abstract class GenericButton
{

    protected $context;

    /**
     * @param \Magento\Backend\Block\Widget\Context $context
     */
    public function __construct(Context $context)
    {
        $this->context = $context;
    }

    /**
     * Return model ID
     *
     * @return int|null
     */
    public function getModelId()
    {
        return $this->context->getRequest()->getParam('category_id');
    }

    /**
     * Generate url by route and parameters
     *
     * @param   string $route
     * @param   array $params
     * @return  string
     */
    public function getUrl($route = '', $params = [])
    {
        return $this->context->getUrlBuilder()->getUrl($route, $params);
    }
}

Now add file at Block/Adminhtml/Category/Edit/DeleteButton.php

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

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

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

class DeleteButton extends GenericButton implements ButtonProviderInterface
{

    /**
     * @return array
     */
    public function getButtonData()
    {
        $data = [];
        if ($this->getModelId()) {
            $data = [
                'label' => __('Delete Category'),
                'class' => 'delete',
                'on_click' => 'deleteConfirm(\'' . __(
                    'Are you sure you want to do this?'
                ) . '\', \'' . $this->getDeleteUrl() . '\')',
                'sort_order' => 20,
            ];
        }
        return $data;
    }

    /**
     * Get URL for delete button
     *
     * @return string
     */
    public function getDeleteUrl()
    {
        return $this->getUrl('*/*/delete', ['category_id' => $this->getModelId()]);
    }
}

Now add file at Block/Adminhtml/Category/Edit/SaveAndContinueButton.php

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

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

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

class SaveAndContinueButton extends GenericButton implements ButtonProviderInterface
{

    /**
     * @return array
     */
    public function getButtonData()
    {
        return [
            'label' => __('Save and Continue Edit'),
            'class' => 'save',
            'data_attribute' => [
                'mage-init' => [
                    'button' => ['event' => 'saveAndContinueEdit'],
                ],
            ],
            'sort_order' => 80,
        ];
    }
}

Now add file at Block/Adminhtml/Category/Edit/SaveButton.php

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

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

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

class SaveButton extends GenericButton implements ButtonProviderInterface
{

    /**
     * @return array
     */
    public function getButtonData()
    {
        return [
            'label' => __('Save Category'),
            'class' => 'save primary',
            'data_attribute' => [
                'mage-init' => ['button' => ['event' => 'save']],
                'form-role' => 'save',
            ],
            'sort_order' => 90,
        ];
    }
}

Now to make DeleteButton work properly, add a file at Controller/Adminhtml/Category/Delete.php

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Category;

class Delete extends \Magelearn\Categoryfaq\Controller\Adminhtml\Category
{

    /**
     * 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('category_id');
        if ($id) {
            try {
                // init model and delete
                $model = $this->_objectManager->create(\Magelearn\Categoryfaq\Model\Category::class);
                $model->load($id);
                $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', ['category_id' => $id]);
            }
        }
        // display error message
        $this->messageManager->addErrorMessage(__('We can\'t find a Category to delete.'));
        // go to grid
        return $resultRedirect->setPath('*/*/');
    }
}

Now we will add different files to manage FAQ questions properly.

Add file at Controller/Adminhtml/Question/Index.php

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Question;

class Index extends \Magento\Backend\App\Action
{

    protected $resultPageFactory;

    /**
     * Constructor
     *
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
        $this->resultPageFactory = $resultPageFactory;
        parent::__construct($context);
    }

    /**
     * Index action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        $resultPage = $this->resultPageFactory->create();
        $resultPage->setActiveMenu('Magelearn_Categoryfaq::magelearn_categoryfaq_question');
        $resultPage->getConfig()->getTitle()->prepend(__("Frequently Asked Questions"));
        return $resultPage;
    }
}

Add file at view/adminhtml/layout/magelearn_categoryfaq_question_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>
        <referenceContainer name="content">
            <uiComponent name="magelearn_categoryfaq_question_listing"/>
        </referenceContainer>
    </body>
</page>

As per the highlighted code above, we will add our uiComponent file at view/adminhtml/ui_component/magelearn_categoryfaq_question_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_categoryfaq_question_listing.magelearn_categoryfaq_question_listing_data_source</item>
        </item>
    </argument>
    <settings>
        <spinner>magelearn_categoryfaq_question_columns</spinner>
        <deps>
            <dep>magelearn_categoryfaq_question_listing.magelearn_categoryfaq_question_listing_data_source</dep>
        </deps>
        <buttons>
            <button name="add">
                <url path="*/*/new"/>
                <class>primary</class>
                <label translate="true">Add new question</label>
            </button>
            <button name="add_category">
                <url path="*/category/new"/>
                <class>primary</class>
                <label translate="true">Add Category</label>
            </button>
        </buttons>
    </settings>
    <dataSource name="magelearn_categoryfaq_question_listing_data_source" component="Magento_Ui/js/grid/provider">
        <settings>
            <storageConfig>
                <param name="indexField" xsi:type="string">question_id</param>
            </storageConfig>
            <updateUrl path="mui/index/render"/>
        </settings>
        <aclResource>Magelearn_Categoryfaq::question</aclResource>
        <dataProvider name="magelearn_categoryfaq_question_listing_data_source" class="Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider">
            <settings>
                <requestFieldName>question_id</requestFieldName>
                <primaryFieldName>question_id</primaryFieldName>
            </settings>
        </dataProvider>
    </dataSource>
    <listingToolbar name="listing_top">
        <settings>
            <sticky>true</sticky>
        </settings>
        <bookmark name="bookmarks"/>
        <columnsControls name="columns_controls"/>
        <exportButton name="export_button"/>
        <filterSearch name="fulltext"/>
        <filters name="listing_filters"/>
        <massaction name="listing_massaction" component="Magento_Ui/js/grid/tree-massactions">
            <action name="delete">
                <settings>
                    <confirm>
                        <message translate="true">Are you sure to delete selected FAQ questions?</message>
                        <title translate="true">Delete items</title>
                    </confirm>
                    <url path="magelearn_categoryfaq/question/massDelete"/>
                    <type>delete</type>
                    <label translate="true">Delete</label>
                </settings>
            </action>
            <action name="massEnable">
                <settings>
                    <url path="magelearn_categoryfaq/question/massEnable"/>
                    <type>enable</type>
                    <label translate="true">Enable</label>
                </settings>
            </action>
            <action name="massDisable">
                <settings>
                    <url path="magelearn_categoryfaq/question/massDisable"/>
                    <type>disable</type>
                    <label translate="true">Disable</label>
                </settings>
            </action>
        </massaction>
        <paging name="listing_paging"/>
    </listingToolbar>
    <columns name="magelearn_categoryfaq_question_columns">
        <settings>
            <editorConfig>
                <param name="selectProvider" xsi:type="string">magelearn_categoryfaq_question_listing.magelearn_categoryfaq_question_listing.magelearn_categoryfaq_question_columns.ids</param>
                <param name="enabled" xsi:type="boolean">true</param>
                <param name="indexField" xsi:type="string">question_id</param>
                <param name="clientConfig" xsi:type="array">
                    <item name="saveUrl" xsi:type="url" path="magelearn_categoryfaq/question/inlineEdit"/>
                    <item name="validateBeforeSave" xsi:type="boolean">false</item>
                </param>
            </editorConfig>
            <childDefaults>
                <param name="fieldAction" xsi:type="array">
                    <item name="provider" xsi:type="string">magelearn_categoryfaq_question_listing.magelearn_categoryfaq_question_listing.magelearn_categoryfaq_question_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>
                </param>
            </childDefaults>
        </settings>
        <selectionsColumn name="ids">
            <settings>
                <indexField>question_id</indexField>
            </settings>
        </selectionsColumn>
        <column name="question_id">
            <settings>
                <filter>text</filter>
                <label translate="true">ID</label>
                <editor>
                    <editorType>text</editorType>
                    <validation>
                        <rule name="required-entry" xsi:type="boolean">false</rule>
                    </validation>
                </editor>
            </settings>
        </column>
        <column name="title">
            <settings>
                <filter>text</filter>
                <label translate="true">Title</label>
                <editor>
                    <editorType>text</editorType>
                    <validation>
                        <rule name="required-entry" xsi:type="boolean">true</rule>
                    </validation>
                </editor>
            </settings>
        </column>
        <column name="answer" class="Magelearn\Categoryfaq\Ui\Component\Listing\Column\Answer">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="bodyTmpl" xsi:type="string">ui/grid/cells/html</item>
                    <item name="filter" xsi:type="string">text</item>
                    <item name="sortOrder" xsi:type="number">100</item>
                    <item name="label" xsi:type="string" translate="true">Answer</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">false</item>
                        </item>
                    </item>
                </item>
            </argument>
        </column>
        <column name="status">
             <argument name="data" xsi:type="array">
                <item name="options" xsi:type="object">Magelearn\Categoryfaq\Model\Question\Status\Options</item>
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">select</item>
                    <item name="component" xsi:type="string">Magelearn_Categoryfaq/js/grid/columns/status</item>
                    <item name="dataType" xsi:type="string">select</item>
                    <item name="editor" xsi:type="string">select</item>
                    <item name="sorting" xsi:type="string">asc</item>
                    <item name="label" xsi:type="string" translate="true">Status</item>
                </item>
            </argument>
        </column>
        <column name="sort_order">
            <settings>
                <filter>text</filter>
                <label translate="true">Sort Order</label>
                <editor>
                    <editorType>text</editorType>
                    <validation>
                        <rule name="required-entry" xsi:type="boolean">true</rule>
                        <rule name="integer" xsi:type="boolean">true</rule>
                    </validation>
                </editor>
            </settings>
        </column>
        <column name="created_at">
            <settings>
                <filter>text</filter>
                <label translate="true">Created At</label>
                <editor>
                    <editorType>text</editorType>
                    <validation>
                        <rule name="required-entry" xsi:type="boolean">false</rule>
                    </validation>
                </editor>
            </settings>
        </column>
        <column name="updated_at">
            <settings>
                <filter>text</filter>
                <label translate="true">Updated At</label>
                <editor>
                    <editorType>text</editorType>
                    <validation>
                        <rule name="required-entry" xsi:type="boolean">false</rule>
                    </validation>
                </editor>
            </settings>
        </column>
        <actionsColumn name="actions" class="Magelearn\Categoryfaq\Ui\Component\Listing\Column\QuestionActions">
            <settings>
                <indexField>question_id</indexField>
                <resizeEnabled>false</resizeEnabled>
                <resizeDefaultWidth>107</resizeDefaultWidth>
            </settings>
        </actionsColumn>
    </columns>
</listing>

Now we will add our massaction files.

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

<?php

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Question;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\ResultFactory;
use Magento\Ui\Component\MassAction\Filter;
use Magelearn\Categoryfaq\Model\ResourceModel\Question\CollectionFactory;
use Magento\Framework\App\Action\HttpPostActionInterface;

/**
 * Class MassDelete
 * @package Magelearn\Categoryfaq\Controller\Adminhtml\Question
 */
class MassDelete extends Action implements HttpPostActionInterface
{
    /**
     * @var Filter
     */
    protected $filter;

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

    /**
     * @param Context $context
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(Context $context, Filter $filter, CollectionFactory $collectionFactory)
    {
        parent::__construct($context);

        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;
    }

    /**
     * Execute Mass Delete Action
     *
     * @return \Magento\Backend\Model\View\Result\Redirect
     * @throws \Magento\Framework\Exception\LocalizedException|\Exception
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());
        $collectionSize = $collection->getSize();
        foreach ($collection as $item) {
            $item->delete();
        }

        $this->messageManager->addSuccessMessage(__('A total of %1 questions have been deleted.', $collectionSize));

        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
        return $resultRedirect->setPath('*/*/');
    }
}

Add file at Controller/Adminhtml/Question/MassDisable.php

<?php

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Question;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\ResultFactory;
use Magento\Ui\Component\MassAction\Filter;
use Magelearn\Categoryfaq\Model\ResourceModel\Question\CollectionFactory;
use Magento\Framework\App\Action\HttpPostActionInterface;

/**
 * Class MassDisable
 * @package Magelearn\Categoryfaq\Controller\Adminhtml\Question
 */
class MassDisable extends Action implements HttpPostActionInterface
{
    /**
     * @var Filter
     */
    protected $filter;

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

    /**
     * @param Context $context
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(Context $context, Filter $filter, CollectionFactory $collectionFactory)
    {
        parent::__construct($context);

        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;
    }

    /**
     * Execute Mass Disable Action
     *
     * @return \Magento\Backend\Model\View\Result\Redirect
     * @throws \Magento\Framework\Exception\LocalizedException|\Exception
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());
        $collectionSize = $collection->getSize();

        foreach ($collection as $item) {
            $item->setStatus(0);
            $item->save();
        }

        $this->messageManager->addSuccessMessage(__('A total of %1 questions have been disabled.', $collectionSize));

        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
        return $resultRedirect->setPath('*/*/');
    }
}

Add file at Controller/Adminhtml/Question/MassEnable.php

<?php

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Question;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\ResultFactory;
use Magento\Ui\Component\MassAction\Filter;
use Magelearn\Categoryfaq\Model\ResourceModel\Question\CollectionFactory;
use Magento\Framework\App\Action\HttpPostActionInterface;

/**
 * Class MassEnable
 * @package Magelearn\Categoryfaq\Controller\Adminhtml\Question
 */
class MassEnable extends Action implements HttpPostActionInterface
{
    /**
     * @var Filter
     */
    protected $filter;

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

    /**
     * @param Context $context
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(Context $context, Filter $filter, CollectionFactory $collectionFactory)
    {
        parent::__construct($context);

        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;
    }

    /**
     * Execute action
     *
     * @return \Magento\Backend\Model\View\Result\Redirect
     * @throws \Magento\Framework\Exception\LocalizedException|\Exception
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());
        $collectionSize = $collection->getSize();

        foreach ($collection as $item) {
            $item->setStatus(1);
            $item->save();
        }

        $this->messageManager->addSuccessMessage(__('A total of %1 questions have been enabled.', $collectionSize));

        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);

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

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

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Question;

class InlineEdit extends \Magento\Backend\App\Action
{

    protected $jsonFactory;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\Controller\Result\JsonFactory $jsonFactory
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\Controller\Result\JsonFactory $jsonFactory
    ) {
        parent::__construct($context);
        $this->jsonFactory = $jsonFactory;
    }

    /**
     * Inline edit action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        /** @var \Magento\Framework\Controller\Result\Json $resultJson */
        $resultJson = $this->jsonFactory->create();
        $error = false;
        $messages = [];
        
        if ($this->getRequest()->getParam('isAjax')) {
            $postItems = $this->getRequest()->getParam('items', []);
            if (!count($postItems)) {
                $messages[] = __('Please correct the data sent.');
                $error = true;
            } else {
                foreach (array_keys($postItems) as $modelid) {
                    /** @var \Magelearn\Categoryfaq\Model\Question $model */
                    $model = $this->_objectManager->create(\Magelearn\Categoryfaq\Model\Question::class)->load($modelid);
                    try {
                        $model->setData(array_merge($model->getData(), $postItems[$modelid]));
                        $model->save();
                    } catch (\Exception $e) {
                        $messages[] = "[Question ID: {$modelid}]  {$e->getMessage()}";
                        $error = true;
                    }
                }
            }
        }
        
        return $resultJson->setData([
            'messages' => $messages,
            'error' => $error
        ]);
    }
}

As per the highlighted code in magelearn_categoryfaq_question_listing.xml file

Add file at Ui/Component/Listing/Column/Answer.php

<?php

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

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

class Answer extends Column
{
    /**
     * Answer constructor.
     * @param ContextInterface $context
     * @param UiComponentFactory $uiComponentFactory    
     * @param array $components
     * @param array $data
     */
    public function __construct(
        ContextInterface $context,
        UiComponentFactory $uiComponentFactory,
        array $components = [],
        array $data = []
    ) { 
        parent::__construct($context, $uiComponentFactory, $components, $data);
    }

    public function prepareDataSource(array $dataSource)
    {
        if (isset($dataSource['data']['items'])) {
            foreach ($dataSource['data']['items'] as & $item) {
                if(isset($item['answer']) && !empty($item['answer'])) {
                    $item[$this->getData('answer')] = html_entity_decode(nl2br($item['answer']));
                }
            }
        }
        return $dataSource;
    }
}

As per the highlighted code in magelearn_categoryfaq_question_listing.xml file

Add file at Model/Question/Status/Options.php

<?php

namespace Magelearn\Categoryfaq\Model\Question\Status;

class Options implements \Magento\Framework\Data\OptionSourceInterface
{
    /**
     * Grid display options.
     *
     * @return array
     */
    public function toOptionArray()
    {
        return [
            ['value' => '0', 'label' => __('Disabled')],
            ['value' => '1', 'label' => __('Enabled')]
        ];
    }
}

Now we will add actionsColumn files.

Add file at Ui/Component/Listing/Column/QuestionActions.php

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

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

class QuestionActions extends \Magento\Ui\Component\Listing\Columns\Column
{

    const URL_PATH_DELETE = 'magelearn_categoryfaq/question/delete';
    protected $urlBuilder;
    const URL_PATH_EDIT = 'magelearn_categoryfaq/question/edit';
    const URL_PATH_DETAILS = 'magelearn_categoryfaq/question/details';

    /**
     * @param \Magento\Framework\View\Element\UiComponent\ContextInterface $context
     * @param \Magento\Framework\View\Element\UiComponentFactory $uiComponentFactory
     * @param \Magento\Framework\UrlInterface $urlBuilder
     * @param array $components
     * @param array $data
     */
    public function __construct(
        \Magento\Framework\View\Element\UiComponent\ContextInterface $context,
        \Magento\Framework\View\Element\UiComponentFactory $uiComponentFactory,
        \Magento\Framework\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) {
                if (isset($item['question_id'])) {
                    $item[$this->getData('name')] = [
                        'edit' => [
                            'href' => $this->urlBuilder->getUrl(
                                static::URL_PATH_EDIT,
                                [
                                    'question_id' => $item['question_id']
                                ]
                            ),
                            'label' => __('Edit')
                        ],
                        'delete' => [
                            'href' => $this->urlBuilder->getUrl(
                                static::URL_PATH_DELETE,
                                [
                                    'question_id' => $item['question_id']
                                ]
                            ),
                            'label' => __('Delete'),
                            'confirm' => [
                                'title' => __('Delete %1',$item['title']),
                                'message' => __('Are you sure you wan\'t to delete a %1 record?', $item['title'])
                            ]
                        ]
                    ];
                }
            }
        }
        
        return $dataSource;
    }
}

Now as highlighted in the above code, to make the delete action work properly

Add file at Controller/Adminhtml/Question/Delete.php

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Question;

class Delete extends \Magelearn\Categoryfaq\Controller\Adminhtml\Question
{

    /**
     * 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('question_id');
        if ($id) {
            try {
                // init model and delete
                $model = $this->_objectManager->create(\Magelearn\Categoryfaq\Model\Question::class);
                $model->load($id);
                $model->delete();
                // display success message
                $this->messageManager->addSuccessMessage(__('You deleted the Question.'));
                // 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', ['question_id' => $id]);
            }
        }
        // display error message
        $this->messageManager->addErrorMessage(__('We can\'t find a Question to delete.'));
        // go to grid
        return $resultRedirect->setPath('*/*/');
    }
}

And as highlighted code in the above file,

Add Controller/Adminhtml/Question.php file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Controller\Adminhtml;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Registry;
use Magelearn\Categoryfaq\Model\QuestionFactory;

abstract class Question extends Action
{
    const ADMIN_RESOURCE = 'Magelearn_Categoryfaq::management';
    
    /**
     * Question Factory
     *
     * @var QuestionFactory
     */
    public $questionFactory;
    
    /**
     * Core registry
     *
     * @var Registry
     */
    public $coreRegistry;

    /**
     * @param QuestionFactory $questionFactory
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\Registry $coreRegistry
     */
    public function __construct(
        QuestionFactory $questionFactory,
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\Registry $coreRegistry
    ) {
        $this->questionFactory = $questionFactory;
        $this->_coreRegistry = $coreRegistry;
        parent::__construct($context);
    }

    /**
     * Init page
     *
     * @param \Magento\Backend\Model\View\Result\Page $resultPage
     * @return \Magento\Backend\Model\View\Result\Page
     */
    public function initPage($resultPage)
    {
        $resultPage->setActiveMenu(self::ADMIN_RESOURCE)
            ->addBreadcrumb(__('Magelearn'), __('Magelearn'))
            ->addBreadcrumb(__('Question'), __('Question'));
        return $resultPage;
    }
    
    /**
     * @param bool $register
     * @param bool $isSave
     *
     * @return bool|\Magelearn\Categoryfaq\Model\Question
     */
    protected function initQuestion($register = false, $isSave = false)
    {
        $questionId = (int)$this->getRequest()->getParam('id');

        /** @var \Magelearn\Categoryfaq\Model\Question $question */
        $question = $this->questionFactory->create();
        if ($questionId) {
            if (!$isSave) {
                $question->load($questionId);
                if (!$question->getId()) {
                    $this->messageManager->addErrorMessage(__('This question no longer exists.'));
                    
                    return false;
                }
            }
        }
        
        if ($register) {
            $this->coreRegistry->register('magelearn_categoryfaq_question', $question);
        }
        
        return $question;
    }
}

To add a new question add a file at view/adminhtml/layout/magelearn_categoryfaq_question_new.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="magelearn_categoryfaq_question_edit"/>
</page>

Add file at Controller/Adminhtml/Question/NewAction.php

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Question;

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 extends Action
{

    protected $resultForwardFactory;

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

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

Now this new action is forwarded to edit action.

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

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Question;

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\Categoryfaq\Controller\Adminhtml\Question;
use Magelearn\Categoryfaq\Model\QuestionFactory;

/**
 * Class Edit
 * @package Magelearn\Categoryfaq\Controller\Adminhtml\Question
 */
class Edit extends Question
{
    /**
     * Question Factory
     *
     * @var QuestionFactory
     */
    public $questionFactory;
    
    /**
     * Page factory
     *
     * @var PageFactory
     */
    public $resultPageFactory;

    /**
     * @param Context $context
     * @param Registry $coreRegistry
     * @param QuestionFactory $questionFactory
     * @param PageFactory $resultPageFactory
     */
    public function __construct(
        Context $context,
        Registry $coreRegistry,
        PageFactory $resultPageFactory,
        QuestionFactory $questionFactory
    ) {
        $this->resultPageFactory = $resultPageFactory;
        parent::__construct($questionFactory, $context, $coreRegistry);
    }

    /**
     * Edit action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        // 1. Get ID and create model
        $id = $this->getRequest()->getParam('question_id');
        $model = $this->questionFactory->create();
        
        // 2. Initial checking
        if ($id) {
            $model->load($id);
            if (!$model->getId()) {
                $this->messageManager->addErrorMessage(__('This Question no longer exists.'));
                /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
                $resultRedirect = $this->resultRedirectFactory->create();
                return $resultRedirect->setPath('*/*/');
            }
        }
        $this->_coreRegistry->register('magelearn_categoryfaq_question', $model);
        
        // 3. Build edit form
        /** @var \Magento\Backend\Model\View\Result\Page $resultPage */
        $resultPage = $this->resultPageFactory->create();
        $this->initPage($resultPage)->addBreadcrumb(
            $id ? __('Edit Question') : __('New Question'),
            $id ? __('Edit Question') : __('New Question')
        );
        $resultPage->getConfig()->getTitle()->prepend(__('Questions'));
        $resultPage->getConfig()->getTitle()->prepend($model->getId() ? __('Edit Question %1', $model->getId()) : __('New Question'));
        return $resultPage;
    }

    public function _isAllowed()
    {
        return $this->_authorization->isAllowed('Magelearn_Categoryfaq::edit');
    }
}

Also, add a file at view/adminhtml/layout/magelearn_categoryfaq_question_edit.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>
        <referenceContainer name="content">
            <block class="Magelearn\Categoryfaq\Block\Adminhtml\Question\Edit" name="question_edit"/>
        </referenceContainer>
    </body>
</page>

Here we will add a Question edit Form with a Widget instead of a UI Component form.

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

<?php
namespace Magelearn\Categoryfaq\Block\Adminhtml\Question;

use Magento\Backend\Block\Widget\Context;
use Magento\Backend\Block\Widget\Form\Container;
use Magento\Framework\Registry;
use Magelearn\Categoryfaq\Model\Question;

/**
 * Class Edit
 * @package Magelearn\Categoryfaq\Block\Adminhtml\Question
 */
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 Question edit block
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_objectId = 'question_id';
        $this->_blockGroup = 'Magelearn_Categoryfaq';
        $this->_controller = 'adminhtml_question';
        parent::_construct();
        //$this->buttonList->remove('delete');
        $question = $this->coreRegistry->registry('magelearn_categoryfaq_question');
        //$this->buttonList->remove('reset');
        $this->buttonList->update('save', 'label', __('Save Question'));
        $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 ($question->getId() && $this->_request->getParam('question_id')) {
            $this->buttonList->update('delete', 'label', __('Delete Question'));
        }
    }
    
    /**
     * Retrieve text for header element depending on loaded Question
     *
     * @return string
     */
    public function getHeaderText()
    {
        /** @var Question $question */
        $question = $this->coreRegistry->registry('magelearn_categoryfaq_question');
        
        if ($question->getId()) {
            return __("Edit Question '%1'", $this->escapeHtml($question->getTitle()));
        }
        
        return __('New Question');
    }
    
    /**
     * Get form action URL
     *
     * @return string
     */
    public function getFormActionUrl()
    {
        /** @var Question $question */
        $question = $this->coreRegistry->registry('magelearn_categoryfaq_question');
        if ($question->getId()) {

            $ar = ['id' => $question->getId()];
            
            return $this->getUrl('*/*/save', $ar);
        }

        return parent::getFormActionUrl();
    }
}

And then add Block/Adminhtml/Question/Edit/Form.php file.

<?php

namespace Magelearn\Categoryfaq\Block\Adminhtml\Question\Edit;

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\Framework\Data\Form\Element\Renderer\RendererInterface;
use Magento\Framework\Data\FormFactory;
use Magento\Framework\Registry;
use Magento\Cms\Model\Wysiwyg\Config;
use Magelearn\Categoryfaq\Block\Adminhtml\Question\Edit\Renderer\Category;

/**
 * Class Form
 * @package Magelearn\Categoryfaq\Block\Adminhtml\Question\Edit
 */
class Form extends Generic
{
    /**
     * Wysiwyg config
     *
     * @var Config
     */
    public $wysiwygConfig;
    
    /**
     * Question constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param FormFactory $formFactory
     * @param Config $wysiwygConfig
     * @param array $data
     */
    public function __construct(
        Context $context,
        Registry $registry,
        FormFactory $formFactory,
        Config $wysiwygConfig,
        array $data = []
        ) {
            $this->wysiwygConfig = $wysiwygConfig;
            
            parent::__construct($context, $registry, $formFactory, $data);
    }
    /**
     * @inheritdoc
     */
    protected function _prepareForm()
    {
        $question = $this->_coreRegistry->registry('magelearn_categoryfaq_question');
        /** @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->setHtmlIdPrefix('question_');
        $form->setFieldNameSuffix('question');
        
        if ($question->getQuestionId()) {
            $fieldset = $form->addFieldset(
                'base_fieldset',
                ['legend' => __('Edit Question Data'), 'class' => 'fieldset-wide']
                );
            $fieldset->addField('question_id', 'hidden', ['name' => 'question_id']);
        } else {
            $fieldset = $form->addFieldset(
                'base_fieldset',
                ['legend' => __('Add New Question'), 'class' => 'fieldset-wide']
                );
        }
        
        $fieldset->addField('status', 'select', [
            'name' => 'status',
            'label' => __('Status'),
            'options' => [0=>__('Disabled'), 1=>__('Enabled')],
            'id' => 'status',
            'title' => __('Status'),
            'required' => false,
        ]);
        if (!$question->hasData('status')) {
            $question->setStatus(1);
        }
        
        $fieldset->addField(
            'title',
            'text',
            [
                'name' => 'title',
                'label' => __('Title'),
                'id' => 'title',
                'title' => __('Title'),
                'class' => 'required-entry',
                'required' => true,
            ]
            );
        
        $fieldset->addField(
            'answer',
            'editor',
            [
                'name' => 'answer',
                'label' => __('Answer'),
                'id' => 'answer',
                'title' => __('Answer'),
                'config' => $this->wysiwygConfig->getConfig([
                    'add_variables' => false,
                    'add_widgets' => true,
                    'add_directives' => true
                ])
            ]
            );
        
        $fieldset->addField('categories_ids', Category::class, [
            'name' => 'categories_ids',
            'label' => __('Categories'),
            'title' => __('Categories'),
        ]);
        if (!$question->getCategoriesIds()) {
            $question->setCategoriesIds($question->getCategoryIds());
        }
        
        $fieldset->addField(
            'sort_order',
            'text',
            [
                'name' => 'sort_order',
                'label' => __('Sort Order'),
                'id' => 'sort_order',
                'title' => __('Sort Order'),
                'class' => 'required-entry',
                'required' => true,
            ]
            );

        $form->addValues($question->getData());
        $form->setUseContainer(true);
        $this->setForm($form);

        return parent::_prepareForm();
    }
}

Now as per the highlighted code above, to add categories drop down

we will add Block/Adminhtml/Question/Edit/Renderer/Category.php file.

<?php

namespace Magelearn\Categoryfaq\Block\Adminhtml\Question\Edit\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\Categoryfaq\Model\ResourceModel\Category\Collection;
use Magelearn\Categoryfaq\Model\ResourceModel\Category\CollectionFactory as QuestionCategoryCollectionFactory;

/**
 * Class Category
 * @package Magelearn\Categoryfaq\Block\Adminhtml\Question\Edit\Renderer
 */
class Category extends Multiselect
{
    /**
     * @var QuestionCategoryCollectionFactory
     */
    public $collectionFactory;

    /**
     * Authorization
     *
     * @var AuthorizationInterface
     */
    public $authorization;

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

    /**
     * Tag constructor.
     *
     * @param Factory $factoryElement
     * @param CollectionFactory $factoryCollection
     * @param Escaper $escaper
     * @param QuestionCategoryCollectionFactory $collectionFactory
     * @param AuthorizationInterface $authorization
     * @param UrlInterface $urlBuilder
     * @param array $data
     */
    public function __construct(
        Factory $factoryElement,
        CollectionFactory $factoryCollection,
        Escaper $escaper,
        QuestionCategoryCollectionFactory $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="question-category-select" class="admin__field" data-bind="scope:\'questionCategory\'" data-index="index">';
        $html .= '<!-- ko foreach: elems() -->';
        $html .= '<input name="question[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_Categoryfaq::category');

        return $this->getData('no_display') || $isNotAllowed;
    }

    /**
     * @return mixed
     */
    public function getCategoriesCollection()
    {
        /* @var $collection Collection */
        $collection = $this->collectionFactory->create();
        $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()->addIdFilter($values);

        $options = [];
        foreach ($collection as $category) {
            $options[] = $category->getId();
        }

        return $options;
    }

    /**
     * Attach Question Category suggest widget initialization
     *
     * @return string
     */
    public function getAfterElementHtml()
    {
        $html = '<script type="text/x-magento-init">
            {
                "*": {
                    "Magento_Ui/js/core/app": {
                        "components": {
                            "questionCategory": {
                                "component": "uiComponent",
                                "children": {
                                    "category_select_tag": {
                                        "component": "Magelearn_Categoryfaq/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": "category_select_tag",
                                                "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_categoryfaq_category_create',
                    'buttons' => 1
                ]
            ) . '",
                                        "autoRender": false,
                                        "ns": "question_new_category_form",
                                        "externalProvider": "question_new_category_form.new_category_form_data_source",
                                        "toolbarContainer": "${ $.parentName }",
                                        "formSubmitType": "ajax"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        </script>';

        return $html;
    }
}

As per the highlighted in the code above, we will add our layout file at view/adminhtml/layout/magelearn_categoryfaq_category_create.xml

<?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="question_new_category_form"/>
        </referenceContainer>
    </body>
</page>

We will add our ui_component file at view/adminhtml/ui_component/question_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">question_new_category_form.new_category_form_data_source</item>
            <item name="deps" xsi:type="string">question_new_category_form.new_category_form_data_source</item>
            <item name="namespace" xsi:type="string">question_new_category_form</item>
        </item>
        <item name="buttons" xsi:type="array">
            <item name="save" xsi:type="string">Magelearn\Categoryfaq\Block\Adminhtml\Question\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\Categoryfaq\Ui\DataProvider\Question\Form\NewCategoryDataProvider</argument>
            <argument name="name" xsi:type="string">new_category_form_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">category_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_categoryfaq/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 code in the above file, we will add our DataProvider class at
Ui/DataProvider/Question/Form/NewCategoryDataProvider.php

<?php

namespace Magelearn\Categoryfaq\Ui\DataProvider\Question\Form;

use Magento\Framework\Phrase;
use Magento\Framework\UrlInterface;
use Magento\Ui\DataProvider\AbstractDataProvider;
use Magelearn\Categoryfaq\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;
    }
}

As per the Highlighted code in question_new_category_form.xml file, we will add
Block/Adminhtml/Question/Edit/Button/CreateCategory.php file.

<?php

namespace Magelearn\Categoryfaq\Block\Adminhtml\Question\Edit\Button;

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

/**
 * Button "Create Category" in "New Category" slide-out panel of a Question page
 * Class CreateCategory
 * @package Magelearn\Categoryfaq\Block\Adminhtml\Question\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
        ];
    }
}

As per the highlighted in the code Block/Adminhtml/Question/Edit/Renderer/Category.php file to add the new category on the fly,

we will add 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);
            }
        }
    });
});

Now to add different buttons on the question edit form, we will add the below files.

Add Block/Adminhtml/Question/Edit/BackButton.php file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Block\Adminhtml\Question\Edit;

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

class BackButton extends GenericButton implements ButtonProviderInterface
{

    /**
     * @return array
     */
    public function getButtonData()
    {
        return [
            'label' => __('Back'),
            'on_click' => sprintf("location.href = '%s';", $this->getBackUrl()),
            'class' => 'back',
            'sort_order' => 10
        ];
    }

    /**
     * Get URL for back (reset) button
     *
     * @return string
     */
    public function getBackUrl()
    {
        return $this->getUrl('*/*/');
    }
}

As per highlighted the code above, add Block/Adminhtml/Question/Edit/GenericButton.php file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Block\Adminhtml\Question\Edit;

use Magento\Backend\Block\Widget\Context;

abstract class GenericButton
{

    protected $context;

    /**
     * @param \Magento\Backend\Block\Widget\Context $context
     */
    public function __construct(Context $context)
    {
        $this->context = $context;
    }

    /**
     * Return model ID
     *
     * @return int|null
     */
    public function getModelId()
    {
        return $this->context->getRequest()->getParam('question_id');
    }

    /**
     * Generate url by route and parameters
     *
     * @param   string $route
     * @param   array $params
     * @return  string
     */
    public function getUrl($route = '', $params = [])
    {
        return $this->context->getUrlBuilder()->getUrl($route, $params);
    }
}

Add Block/Adminhtml/Question/Edit/DeleteButton.php file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Block\Adminhtml\Question\Edit;

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

class DeleteButton extends GenericButton implements ButtonProviderInterface
{

    /**
     * @return array
     */
    public function getButtonData()
    {
        $data = [];
        if ($this->getModelId()) {
            $data = [
                'label' => __('Delete Question'),
                'class' => 'delete',
                'on_click' => 'deleteConfirm(\'' . __(
                    'Are you sure you want to do this?'
                ) . '\', \'' . $this->getDeleteUrl() . '\')',
                'sort_order' => 20,
            ];
        }
        return $data;
    }

    /**
     * Get URL for delete button
     *
     * @return string
     */
    public function getDeleteUrl()
    {
        return $this->getUrl('*/*/delete', ['question_id' => $this->getModelId()]);
    }
}

Add Block/Adminhtml/Question/Edit/SaveAndContinueButton.php file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Block\Adminhtml\Question\Edit;

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

class SaveAndContinueButton extends GenericButton implements ButtonProviderInterface
{

    /**
     * @return array
     */
    public function getButtonData()
    {
        return [
            'label' => __('Save and Continue Edit'),
            'class' => 'save',
            'data_attribute' => [
                'mage-init' => [
                    'button' => ['event' => 'saveAndContinueEdit'],
                ],
            ],
            'sort_order' => 80,
        ];
    }
}

Add Block/Adminhtml/Question/Edit/SaveButton.php file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Block\Adminhtml\Question\Edit;

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

class SaveButton extends GenericButton implements ButtonProviderInterface
{

    /**
     * @return array
     */
    public function getButtonData()
    {
        return [
            'label' => __('Save Question'),
            'class' => 'save primary',
            'data_attribute' => [
                'mage-init' => ['button' => ['event' => 'save']],
                'form-role' => 'save',
            ],
            'sort_order' => 90,
        ];
    }
}

And to save the question data properly, we will add Controller/Adminhtml/Question/Save.php file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Question;

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\Categoryfaq\Model\Question as QuestionModel;
use Magelearn\Categoryfaq\Model\QuestionFactory;
use Magelearn\Categoryfaq\Controller\Adminhtml\Question;
use RuntimeException;

/**
 * Class Save
 * @package Magelearn\Categoryfaq\Controller\Adminhtml\Question
 */
class Save extends Question
{
    /**
     * JS helper
     *
     * @var Js
     */
    public $jsHelper;
    
    /**
     * @var DateTime
     */
    public $date;

    protected $dataPersistor;
    
    /**
     * @var TimezoneInterface
     */
    protected $timezone;

    /**
     * @param Context $context
     * @param Registry $coreRegistry
     * @param QuestionFactory $questionFactory
     * @param DateTime $date
     * @param TimezoneInterface $timezone
     * @param \Magento\Framework\App\Request\DataPersistorInterface $dataPersistor
     */
    public function __construct(
        Context $context,
        Registry $coreRegistry,
        QuestionFactory $questionFactory,
        Js $jsHelper,
        \Magento\Framework\App\Request\DataPersistorInterface $dataPersistor,
        DateTime $date,
        TimezoneInterface $timezone
    ) {
        $this->jsHelper     = $jsHelper;
        $this->dataPersistor = $dataPersistor;
        $this->date         = $date;
        $this->timezone     = $timezone;
        parent::__construct($questionFactory, $context, $coreRegistry);
    }

    /**
     * Save action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultRedirectFactory->create();
        $action         = $this->getRequest()->getParam('action');
        
        if ($data = $this->getRequest()->getPost('question')) {
            /** @var QuestionModel $question */
            $question = $this->initQuestion(false, true);

            $this->prepareData($question, $data);
        
            $this->_eventManager->dispatch(
                'magelearn_categoryfaq_question_prepare_save',
                ['question' => $question, 'request' => $this->getRequest()]
                );

            try {
                if (empty($action) || $action === 'add') {
                    $question->save();
                    $this->messageManager->addSuccessMessage(__('The question has been saved.'));
                }

                $this->dataPersistor->clear('magelearn_categoryfaq_question');
        
                if ($this->getRequest()->getParam('back')) {
                    return $resultRedirect->setPath('*/*/edit', ['question_id' => $question->getId()]);
                } else {
                    return $resultRedirect->setPath('magelearn_categoryfaq/*/');
                }
                
                return $resultRedirect->setPath('*/*/');
            } catch (RuntimeException $e) {
                $this->messageManager->addErrorMessage($e->getMessage());
            } catch (Exception $e) {
                $this->messageManager->addExceptionMessage($e, __('Something went wrong while saving the Question.'));
            }
        
            $this->dataPersistor->set('magelearn_categoryfaq_question', $data);
            return $resultRedirect->setPath('magelearn_categoryfaq/*/edit', ['question_id' => $this->getRequest()->getParam('question_id'), '_current' => true]);
        }
        return $resultRedirect->setPath('*/*/');
    }
    /**
     * @param QuestionModel $question
     * @param array $data
     *
     * @return $this
     * @throws LocalizedException
     */
    protected function prepareData($question, $data = [])
    {
        
        /** Set specify field data */
        $data['categories_ids'] = (isset($data['categories_ids']) && $data['categories_ids']) ? explode(
            ',',
        $data['categories_ids'] ?? ''
        ) : [];
        
        if ($question->getCreatedAt() == null) {
            $data['created_at'] = $this->date->date();
        }
        $data['updated_at'] = $this->date->date();

        $question->addData($data);
        
        return $this;
    }
}

Frontend

For the Frontend display, we will add the below files.
Add etc/frontend/di.xml file to display FAQ link on Top menu.
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Theme\Block\Html\Topmenu">
        <plugin name="faq-topmenu" type="Magelearn\Categoryfaq\Plugin\Block\Topmenu"/>
    </type>
</config>
Add etc/frontend/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="standard">
        <route id="faq" frontName="faq">
            <module name="Magelearn_Categoryfaq"/>
        </route>
    </router>
</config>
Add view/frontend/layout/faq_index_index.xml file.
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-right"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="styles"/>
    <head>
        <title>FAQ — Frequently Asked Questions</title>
        <css src="Magelearn_Categoryfaq::css/accordion.css"/>
    </head>
    <body>
        <referenceContainer name="content">
            <block name="faq_block_questions" template="Magelearn_Categoryfaq::questions.phtml">
                <arguments>
                    <argument name="viewModel" xsi:type="object">Magelearn\Categoryfaq\ViewModel\Questions</argument>
                </arguments>
            </block>
        </referenceContainer>
    </body>
</page>
Add Controller/Index/Index.php file.
<?php

namespace Magelearn\Categoryfaq\Controller\Index;

use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;
use Magento\Framework\App\Action\HttpGetActionInterface;

/**
 * Class Index
 *
 * @package Magelearn\Categoryfaq\Controller\Index
 */
class Index extends Action implements HttpGetActionInterface
{
    /**
     * @var PageFactory
     */
    protected $resultPageFactory;

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

    public function execute()
    {
        return $this->resultPageFactory->create();
    }
}
As per the highlighted code in faq_index_index.xml file,

We will add our CSS file at view/frontend/web/css/accordion.css

And ViewModel file at ViewModel/Questions.php
<?php

declare(strict_types=1);
 
namespace Magelearn\Categoryfaq\ViewModel;

use Magento\Framework\DataObject;
use Magento\Framework\View\Element\Block\ArgumentInterface;
use Magelearn\Categoryfaq\Model\ResourceModel\Question\CollectionFactory;
use Magelearn\Categoryfaq\Model\CategoryFactory;
 
class Questions extends DataObject implements ArgumentInterface
{
    /*
     * This label won't be displayed in the frontend block
     */
    public const MAIN_LABEL = 'Default';
    
    /**
     * @var CollectionFactory
     */
    protected $collectionFactory;
    
    /**
     * @var CategoryFactory
     */
    public $categoryFactory;
    
    public function __construct(
        CollectionFactory $collectionFactory,
        CategoryFactory $categoryFactory
        ) {
            $this->collectionFactory = $collectionFactory;
            $this->categoryFactory    = $categoryFactory;
            parent::__construct();
    }
    
    /**
     * Get All Questions
     *
     * @return \Magento\Framework\DataObject[]
     */
    public function getItems()
    {
        $questionCollection = $this->collectionFactory->create();
        $questionCollection->addFieldToFilter('main_table.status', 1);
        $questionCollection->setOrder('sort_order', 'DESC');
        
        return $questionCollection->getItems();
    }
    /**
     * Get list category html of question
     *
     * @param Question $question
     *
     * @return string|null
     */
    public function getQuestionCategoryHtml($question)
    {
        $categoryHtml = [];
        
        try {
            if (!$question->getCategoryIds()) {
                return null;
            }
            
            $categories = $this->getCategoryCollection($question->getCategoryIds());
            foreach ($categories as $_cat) {
                $categoryHtml[] = $_cat->getName();
            }
        } catch (Exception $e) {
            return null;
        }
        
        return implode(', ', $categoryHtml);
    }
    /**
     * @param $array
     *
     * @return \Magento\Sales\Model\ResourceModel\Collection\AbstractCollection
     */
    public function getCategoryCollection($array)
    {
        try {
            $collection = $this->getObjectList()
            ->addFieldToFilter('category_id', ['in' => $array]);
            
            return $collection;
        } catch (Exception $exception) {
            $this->_logger->error($exception->getMessage());
        }
        
        return null;
    }
    
    public function getObjectList($storeId = null)
    {
        $collection = $this->categoryFactory
        ->create()
        ->getCollection()
        ->addFieldToFilter('status', 1);
        
        return $collection;
    }
}

And last we will add a template file at,

view/frontend/templates/questions.phtml

<?php

use Magelearn\Categoryfaq\ViewModel\Questions;
use Magento\Framework\View\Element\Template;

/** @var Template $block */
$viewModel = $block->getData('viewModel');
$items = $viewModel->getItems();
?>
<div class="container">
    <?php foreach ($items as $question): ?>
    <div class="accordion">
        <div class="accordion-item">
            <a><?php echo $question->getTitle(); ?></a>
            <div class="content">
                <?php echo $question->getAnswer();
                $categoryHtml = $viewModel->getQuestionCategoryHtml($question);
                ?>
                <?php if ($categoryHtml && $categoryHtml != '') : ?>
                    <?php echo '<p><b><i>Categories: '.$categoryHtml.'</i></b></p>' ?>
                <?php endif; ?>
            </div>
        </div>
    <?php endforeach; ?>
    </div>
</div>
<script>
    require(['jquery', 'jquery/ui'], function($) {
        items = document.querySelectorAll(".accordion a");

        function toggleAccordion(){
            this.classList.toggle('active');
            this.nextElementSibling.classList.toggle('active');
        }

        items.forEach(item => item.addEventListener('click', toggleAccordion));
    });
</script>
As per the code Highlighted in etc/frontend/di.xml file,
We will add a Plugin file at Plugin/Block/Topmenu.php
<?php

namespace Magelearn\Categoryfaq\Plugin\Block;

use Magento\Framework\Data\Tree\NodeFactory;
use Magento\Framework\UrlInterface;

class Topmenu
{
    /**
     * @var NodeFactory
     */
    protected $nodeFactory;

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

    /**
     * Topmenu constructor.
     *
     * @param NodeFactory $nodeFactory
     * @param UrlInterface $urlBuilder
     */
    public function __construct(
        NodeFactory $nodeFactory,
        UrlInterface $urlBuilder
    ) {
        $this->nodeFactory = $nodeFactory;
        $this->urlBuilder = $urlBuilder;
    }

    /**
     * Add a new node to the end of the Top Menu
     *
     * @param \Magento\Theme\Block\Html\Topmenu $subject
     * @param string $outermostClass
     * @param string $childrenWrapClass
     * @param int $limit
     */
    public function beforeGetHtml(
        \Magento\Theme\Block\Html\Topmenu $subject,
        $outermostClass = '',
        $childrenWrapClass = '',
        $limit = 0
    ) {
        $node = $this->nodeFactory->create(
            [
                'data'    => $this->getNodeAsArray(),
                'idField' => 'id',
                'tree'    => $subject->getMenu()->getTree()
            ]
        );
        $subject->getMenu()->addChild($node);
    }

    /**
     * Create a new node to be added to the Top Menu
     *
     * @return array
     */
    protected function getNodeAsArray()
    {
        return [
            'name' => __('FAQ'),
            'id'   => 'magelearn-faq',
            'url'  => $this->urlBuilder->getUrl('faq'),
            'has_active' => false
        ];
    }
}
0 Comments On "Advanced FAQ Management with FAQ's category image upload and multiple category assignment for FAQ - Magento2"

Back To Top