Magento2 | PWA | GraphQL

FAQ (frequently asked questions) By Category with jQuery Accordion Magento2 Module Including GraphQL


This Magento2 FAQ module provides complete FAQ solution to display frequently asked questions grouped by categories. It will be displayed on separate FAQ page using a custom jQuery Accordion.







Let's start it by creating custom extension. 

You can find complete module on Github at Magelearn_Categoryfaq

Create 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">
		<sequence>
			<module name = "Magento_GraphQl"/>
		</sequence>
	</module>
</config>
We will going to display our custom FAQ page on front end. That page serves data from database table.

So, first let's start by creating a db schema script for our custom table.

Create db_schema.xml file in etc folder.
here we will define property of all database fields and indexes as well as foreign key relationship.
<?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_category" resource="default" engine="innodb" comment="magelearn_categoryfaq_category Table">
		<column name="id" nullable="false" xsi:type="int" comment="ID" identity="true"/>
		<index referenceId="MAGELEARN_CATEGORYFAQ_CATEGORY_ID" indexType="btree">
			<column name="id"/>
		</index>
		<constraint xsi:type="primary" referenceId="PRIMARY">
			<column name="id"/>
		</constraint>
		<column name="name" nullable="false" xsi:type="text" comment="Name"/>
		<column name="description" nullable="true" xsi:type="text" comment="Description"/>
		<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_NAME" indexType="fulltext">
            <column name="name"/>
        </index>
        <index referenceId="MAGELEARN_CATEGORYFAQ_DESCRIPTION" indexType="fulltext">
            <column name="description"/>
        </index>
	</table>
	<table name="magelearn_categoryfaq_question" resource="default" engine="innodb" comment="magelearn_categoryfaq_question Table">
		<column name="id" nullable="true" xsi:type="int" comment="ID" identity="true"/>
		<constraint xsi:type="primary" referenceId="PRIMARY">
			<column name="id"/>
		</constraint>
		<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="category_id" nullable="true" xsi:type="int" comment="Category associated with this question" 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_QUESTION_TITLE" indexType="fulltext">
            <column name="title"/>
        </index>
        <index referenceId="MAGELEARN_CATEGORYFAQ_QUESTION_ANSWER" indexType="fulltext">
            <column name="answer"/>
        </index>
        <constraint xsi:type="foreign" referenceId="MAGELEARN_CATEGORYFAQ_QUESTION_CATEGORY_ID_MAGELEARN_CATEGORYFAQ_ID" table="magelearn_categoryfaq_question"
               column="category_id" referenceTable="magelearn_categoryfaq_category" referenceColumn="id" onDelete="SET NULL"/>
	</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;
use Magento\Framework\Setup\Patch\PatchRevertableInterface;

class CreateDefaultSamples implements DataPatchInterface, PatchRevertableInterface
{
    /**
     * @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,
                 'category_id' => 1,
                 'sort_order'  => 0
             ]
         ];
         $this->moduleDataSetup->getConnection()->insertArray(
             $this->moduleDataSetup->getTable('magelearn_categoryfaq_question'),
             ['title', 'answer','status','category_id','sort_order'],
             $question_data
             );  
         
        $this->moduleDataSetup->endSetup();
    }
    /**
     * @inheritdoc
     */
    public function revert()
    {
        $this->moduleDataSetup->getConnection()->startSetup();
        
        $this->moduleDataSetup->getConnection()->endSetup();
    }
    public function getAliases()
    {
        return [];
    }
    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');
        }
    }
}

Now, create di.xml file inside etc folder. 

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

<?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>
</config>
Now we will first create files which are necessary for backend management. We will start to create files as per defined in etc/di.xml nodes.

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 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);
}
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);
}
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);
}
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 UPDATED_AT = 'updated_at';
    const DESCRIPTION = 'description';
    const ID = 'id';
    const CREATED_AT = 'created_at';
    const SORT_ORDER = 'sort_order';

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

    /**
     * Set id
     * @param string $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 sort_order
     * @return string|null
     */
    public function getSortOrder();

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

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

    /**
     * Set status
     * @param string $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 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 CATEGORY_ID = 'category_id';
    const STATUS = 'status';
    const UPDATED_AT = 'updated_at';
    const ID = 'id';
    const TITLE = 'title';
    const ANSWER = 'answer';
    const CREATED_AT = 'created_at';
    const SORT_ORDER = 'sort_order';

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

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

    /**
     * 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 string|null
     */
    public function getStatus();

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

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

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

    /**
     * Get category_id
     * @return string|null
     */
    public function getCategoryId();

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

    /**
     * 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);
}
Now Create CategoryRepository.php file inside Model folder.
<?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 QuestionRepository.php file inside Model folder.
<?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 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;


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

    /**
     * @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));
    }
}
Create Category.php file inside Model folder.
<?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::ID);
    }

    /**
     * @inheritDoc
     */
    public function setId($id)
    {
        return $this->setData(self::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);
    }

    /**
     * @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 Question.php file inside Model folder.
<?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 Magento\Framework\Registry;
use Magento\Framework\Stdlib\DateTime\DateTimeFactory;

class Question extends AbstractModel implements QuestionInterface {
    
    /**
     * @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\Question::class);
    }

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

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

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

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

    /**
     * @inheritDoc
     */
    public function getAnswer()
    {
        return $this->getData(self::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);
    }

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

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

    /**
     * @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 Category.php file inside Model/ResourceModel folder.
<?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', 'id');
    }
}
Create Question.php file inside Model/ResourceModel folder.
<?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 Question extends AbstractDb
{

    /**
     * @inheritDoc
     */
    protected function _construct()
    {
        $this->_init('magelearn_categoryfaq_question', 'id');
    }
}
Create file at Model/ResourceModel/Category/Collection.php
<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Model\ResourceModel\Category;

use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;

class Collection extends AbstractCollection
{

    /**
     * @inheritDoc
     */
    protected $_idFieldName = 'id';

    /**
     * @inheritDoc
     */
    protected function _construct()
    {
        $this->_init(
            \Magelearn\Categoryfaq\Model\Category::class,
            \Magelearn\Categoryfaq\Model\ResourceModel\Category::class
        );
    }
    
    /**
     * Initialize select object
     *
     * @return Collection
     */
    protected function _initSelect()
    {
        parent::_initSelect();
        return $this;
    }
}
Create file at Model/ResourceModel/Question/Collection.php
<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\Categoryfaq\Model\ResourceModel\Question;

use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;

class Collection extends AbstractCollection
{
    const TABLE_NAME = 'magelearn_categoryfaq_category';

    /**
     * @inheritDoc
     */
    protected $_idFieldName = 'id';

    /**
     * @inheritDoc
     */
    protected function _construct()
    {
        $this->_init(
            \Magelearn\Categoryfaq\Model\Question::class,
            \Magelearn\Categoryfaq\Model\ResourceModel\Question::class
        );
    }
    
    /**
     * Initialize select object
     *
     * @return Collection
     */
    protected function _initSelect()
    {
        parent::_initSelect();
        
        $this->addFilterToMap('id', 'main_table.id');
        $this->getSelect()->joinLeft(
            ['categories' => $this->getTable(self::TABLE_NAME)],
            'main_table.category_id = categories.id',
            ['name']
            )->columns(
                [
                    'categoryName' => 'categories.name',
                    'id' => 'main_table.id',
                    'created_at' => 'main_table.created_at',
                    'updated_at' => 'main_table.updated_at',
                    'status' => 'main_table.status'
                ]
                );
            
            return $this;
    }
}
Create file at Model/ResourceModel/Question/Grid/Collection.php
<?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_category';
    
    /**
     * 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
        );
    }

    /**
     * Initialize select object
     *
     * @return Collection
     */
    protected function _initSelect()
    {
        parent::_initSelect();

        $this->addFilterToMap('id', 'main_table.id');
        $this->getSelect()->joinLeft(
            ['categories' => $this->getTable(self::TABLE_NAME)],
            'main_table.category_id = categories.id',
            ['name']
        )->columns(
            [
                'categoryName' => 'categories.name',
                'id' => 'main_table.id',
                'created_at' => 'main_table.created_at',
                'updated_at' => 'main_table.updated_at',
                'status' => 'main_table.status']
        );

        return $this;
    }
}

Now we will check files to create Admin menu and ui_component files used in Admin data listing.

Create 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_category" title="Categories" module="Magelearn_Categoryfaq" sortOrder="9999" resource="Magelearn_Categoryfaq::magelearn_categoryfaq_category" parent="Magelearn_Categoryfaq::management" action="magelearn_categoryfaq/category/index"/>
		<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"/>
	</menu>
</config>

Add etc/acl.xml

<?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::category" title="category" sortOrder="10">
					<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 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>
		</resources>
	</acl>
</config>

Now Create routes file for admin at etc/adminhtml/routes.xml

<?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>

Now as per highlighted in menu.xml file create controller and layout files.

Create 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;
    }
}

Create Controller/Adminhtml/Question/Index.php file.

<?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;
    }
}

Now add layout file at 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>

Now add layout file at view/adminhtml/layout/magelearn_categoryfaq_question_index.xml file.

<?xml version="1.0" ?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
	<update handle="styles"/>
	<body>
		<referenceContainer name="content">
			<uiComponent name="magelearn_categoryfaq_question_listing"/>
		</referenceContainer>
	</body>
</page>

Now add category_listing ui_component 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">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>id</requestFieldName>
				<primaryFieldName>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">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>id</indexField>
			</settings>
		</selectionsColumn>
		<column name="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>
		<actionsColumn name="actions" class="Magelearn\Categoryfaq\Ui\Component\Listing\Column\CategoryActions">
			<settings>
				<indexField>id</indexField>
				<resizeEnabled>false</resizeEnabled>
				<resizeDefaultWidth>107</resizeDefaultWidth>
			</settings>
		</actionsColumn>
		<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="component" xsi:type="string">Magento_Ui/js/grid/columns/select</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>
            </argument>
			<settings>
				<filter>select</filter>
				<label translate="true">Status</label>
				<editor>
					<validation>
						<rule name="required-entry" xsi:type="boolean">false</rule>
					</validation>
				</editor>
			</settings>
		</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>
	</columns>
</listing>

Now as per highlighted code we will create file to fetch the status options. Create file at Model/Category/Status/Options.php

<?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')]
        ];
    }
}

For Massactions,

Create file at 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('*/*/');
    }
}

Create file at 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('*/*/');
    }
}

Create file at 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('*/*/');
    }
}
Create file at Controller/Adminhtml/Category/InlineEdit.php file.
<?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
        ]);
    }
}

We will also provide our action column links at 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['id'])) {
                    $item[$this->getData('name')] = [
                        'edit' => [
                            'href' => $this->urlBuilder->getUrl(
                                static::URL_PATH_EDIT,
                                [
                                    'id' => $item['id']
                                ]
                            ),
                            'label' => __('Edit')
                        ]
                    ];
                }
            }
        }
        
        return $dataSource;
    }
}

Now as per highlighted code in magelearn_categoryfaq_category_listing.xml file, we will add our new action controller and layout files.

First Create controller file at Controller/Adminhtml/Category/NewAction.php

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

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Category;

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
     */
    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');
    }
}

This file extends Magelearn/Categoryfaq/Controller/Adminhtml/Category class so we will create our Controller file at Magelearn/Categoryfaq/Controller/Adminhtml/Category.php

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

namespace Magelearn\Categoryfaq\Controller\Adminhtml;

abstract class Category extends \Magento\Backend\App\Action
{

    const ADMIN_RESOURCE = 'Magelearn_Categoryfaq::management';
    protected $_coreRegistry;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\Registry $coreRegistry
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\Registry $coreRegistry
    ) {
        $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;
    }
}

Now 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>

Note that Controller/Adminhtml/Category/NewAction.php redirects to edit action, so we will create necessary files for Edit action.

Create 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;

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
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\Registry $coreRegistry,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
        $this->resultPageFactory = $resultPageFactory;
        parent::__construct($context, $coreRegistry);
    }

    /**
     * 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;
    }
}

Now layout file magelearn_categoryfaq_category_new.xml file targeting edit xml file (<update handle="magelearn_categoryfaq_category_edit"/>).

Create 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 Add xml file for category edit form. create 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>id</requestFieldName>
				<primaryFieldName>id</primaryFieldName>
			</settings>
		</dataProvider>
	</dataSource>
	<fieldset name="general">
		<settings>
			<label>General</label>
		</settings>
		<field name="status" formElement="checkbox" sortOrder="50">
			<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>
		<field name="name" formElement="textarea" 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>text</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>
	</fieldset>
</form>

As per highlighted code above we will add our DataProvider 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;

class DataProvider extends AbstractDataProvider
{

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

    /**
     * @var array
     */
    protected $loadedData;
    /**
     * @inheritDoc
     */
    protected $collection;


    /**
     * @param string $name
     * @param string $primaryFieldName
     * @param string $requestFieldName
     * @param CollectionFactory $collectionFactory
     * @param DataPersistorInterface $dataPersistor
     * @param array $meta
     * @param array $data
     */
    public function __construct(
        $name,
        $primaryFieldName,
        $requestFieldName,
        CollectionFactory $collectionFactory,
        DataPersistorInterface $dataPersistor,
        array $meta = [],
        array $data = []
    ) {
        $this->collection = $collectionFactory->create();
        $this->dataPersistor = $dataPersistor;
        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->getId()] = $model->getData();
        }
        $data = $this->dataPersistor->get('magelearn_categoryfaq_category');
        
        if (!empty($data)) {
            $model = $this->collection->getNewEmptyItem();
            $model->setData($data);
            $this->loadedData[$model->getId()] = $model->getData();
            $this->dataPersistor->clear('magelearn_categoryfaq_category');
        }
        
        return $this->loadedData;
    }
}

Add Save action 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\Exception\LocalizedException;

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

    protected $dataPersistor;

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

    /**
     * Save action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultRedirectFactory->create();
        $data = $this->getRequest()->getPostValue();
        if ($data) {
            $id = $this->getRequest()->getParam('category_id');
        
            $model = $this->_objectManager->create(\Magelearn\Categoryfaq\Model\Category::class)->load($id);
            if (!$model->getId() && $id) {
                $this->messageManager->addErrorMessage(__('This Category no longer exists.'));
                return $resultRedirect->setPath('*/*/');
            }
        
            $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 add different button action files.
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('*/*/');
    }
}

Add 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);
    }
}

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()]);
    }
}

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,
        ];
    }
}

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 as per defined in magelearn_categoryfaq_question_index.xml file we will add our ui_component 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">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>id</requestFieldName>
				<primaryFieldName>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">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>id</indexField>
			</settings>
		</selectionsColumn>
		<column name="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">Magento_Ui/js/grid/columns/select</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="categoryName">
			<settings>
				<filter>text</filter>
				<label translate="true">Category</label>
				<editor>
					<editorType>text</editorType>
					<validation>
						<rule name="required-entry" 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>id</indexField>
				<resizeEnabled>false</resizeEnabled>
				<resizeDefaultWidth>107</resizeDefaultWidth>
			</settings>
		</actionsColumn>
	</columns>
</listing>

Now as per highlighted code above we will add Ui/Component/Listing/Column/Answer.php file.

<?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) {
                $item[$this->getData('answer')] = html_entity_decode(nl2br($item['answer']));
            }
        }
        return $dataSource;
    }
}

Also add Model/Question/Status/Options.php file.

<?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 different massaction files for Questions.
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 Ui/Component/Listing/Column/QuestionActions.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 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['id'])) {
                    $item[$this->getData('name')] = [
                        'edit' => [
                            'href' => $this->urlBuilder->getUrl(
                                static::URL_PATH_EDIT,
                                [
                                    'id' => $item['id']
                                ]
                            ),
                            'label' => __('Edit')
                        ],
                        'delete' => [
                            'href' => $this->urlBuilder->getUrl(
                                static::URL_PATH_DELETE,
                                [
                                    'id' => $item['id']
                                ]
                            ),
                            'label' => __('Delete'),
                            'confirm' => [
                                'title' => __('Delete "${ $.$data.title }"'),
                                'message' => __('Are you sure you wan\'t to delete a "${ $.$data.title }" record?')
                            ]
                        ]
                    ];
                }
            }
        }
        
        return $dataSource;
    }
}

Now To provide Delete action properly for Questions, we will 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('*/*/');
    }
}

To Provide proper extend class for this Delete.php file, we will add Controller/Adminhtml/Question.php

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

namespace Magelearn\Categoryfaq\Controller\Adminhtml;

abstract class Question extends \Magento\Backend\App\Action
{

    protected $_coreRegistry;
    const ADMIN_RESOURCE = 'Magelearn_Categoryfaq::management';

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\Registry $coreRegistry
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\Registry $coreRegistry
    ) {
        $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;
    }
}

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

<?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
        ]);
    }
}
Now we will add our file for New Action 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;

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

    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');
    }
}

This NewAction.php file is redirect to Edit action.

For that first we will add 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;

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

    protected $resultPageFactory;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\Registry $coreRegistry
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\Registry $coreRegistry,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
        $this->resultPageFactory = $resultPageFactory;
        parent::__construct($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->_objectManager->create(\Magelearn\Categoryfaq\Model\Question::class);
        
        // 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;
    }
}

And layout for new question 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>

Its Redirects to edit xml file. So we will add view/adminhtml/layout/magelearn_categoryfaq_question_edit.xml file.

<?xml version="1.0" ?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
	<update handle="styles"/>
	<body>
		<referenceContainer name="content">
			<uiComponent name="magelearn_categoryfaq_question_form"/>
		</referenceContainer>
	</body>
</page>

Now we will add our ui_component file at view/adminhtml/ui_component/magelearn_categoryfaq_question_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_question_form.question_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\Question\Edit\BackButton"/>
			<button name="delete" class="Magelearn\Categoryfaq\Block\Adminhtml\Question\Edit\DeleteButton"/>
			<button name="save" class="Magelearn\Categoryfaq\Block\Adminhtml\Question\Edit\SaveButton"/>
			<button name="save_and_continue" class="Magelearn\Categoryfaq\Block\Adminhtml\Question\Edit\SaveAndContinueButton"/>
		</buttons>
		<namespace>magelearn_categoryfaq_question_form</namespace>
		<dataScope>data</dataScope>
		<deps>
			<dep>magelearn_categoryfaq_question_form.question_form_data_source</dep>
		</deps>
	</settings>
	<dataSource name="question_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="question_form_data_source" class="Magelearn\Categoryfaq\Model\Question\DataProvider">
			<settings>
				<requestFieldName>id</requestFieldName>
				<primaryFieldName>id</primaryFieldName>
			</settings>
		</dataProvider>
	</dataSource>
	<fieldset name="general">
		<settings>
			<label>General</label>
		</settings>
		<field name="status" formElement="checkbox" sortOrder="80">
			<argument name="data" xsi:type="array">
				<item name="config" xsi:type="array">
					<item name="source" xsi:type="string">question</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>
		<field name="title" formElement="textarea" sortOrder="90">
			<argument name="data" xsi:type="array">
				<item name="config" xsi:type="array">
					<item name="source" xsi:type="string">question</item>
				</item>
			</argument>
			<settings>
				<dataType>text</dataType>
				<label translate="true">Title</label>
				<dataScope>title</dataScope>
				<validation>
					<rule name="required-entry" xsi:type="boolean">true</rule>
				</validation>
			</settings>
		</field>
		<field name="answer" formElement="wysiwyg" sortOrder="100">
			<argument name="data" xsi:type="array">
				<item name="config" xsi:type="array">
					<item name="source" xsi:type="string">question</item>
					<item name="wysiwygConfigData" xsi:type="array">
                        <item name="height" xsi:type="string">100px</item>
                        <item name="add_variables" xsi:type="boolean">true</item>
                        <item name="add_widgets" xsi:type="boolean">true</item>
                        <item name="add_images" xsi:type="boolean">true</item>
                        <item name="add_directives" xsi:type="boolean">true</item>
                    </item>
				</item>
			</argument>
			<settings>
				<dataType>text</dataType>
				<label translate="true">Answer</label>
				<dataScope>answer</dataScope>
			</settings>
			<formElements>
                <wysiwyg>
                    <settings>
                        <rows>8</rows>
                        <wysiwyg>true</wysiwyg>
                    </settings>
                </wysiwyg>
            </formElements>
		</field>
		<field name="category_id" formElement="input" sortOrder="120">
			<argument name="data" xsi:type="array">
			<item name="options" xsi:type="object">Magelearn\Categoryfaq\Model\Category\Source\Categories</item>
				<item name="config" xsi:type="array">
					<item name="formElement" xsi:type="string">select</item>
					<item name="source" xsi:type="string">question</item>
					<item name="default" xsi:type="string">0</item>
				</item>
			</argument>
			<settings>
				<dataType>text</dataType>
				<label translate="true">Category</label>
				<dataScope>category_id</dataScope>
				<validation>
					<rule name="required-entry" xsi:type="boolean">true</rule>
				</validation>
			</settings>
		</field>
		<field name="sort_order" formElement="input" sortOrder="130">
			<argument name="data" xsi:type="array">
				<item name="config" xsi:type="array">
					<item name="source" xsi:type="string">question</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>
	</fieldset>
</form>

As per highlighted code above we will add different action buttons 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('*/*/');
    }
}

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,
        ];
    }
}

To Save the question we will add our file at Controller/Adminhtml/Question/Save.php

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

namespace Magelearn\Categoryfaq\Controller\Adminhtml\Question;

use Magento\Framework\Exception\LocalizedException;

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

    protected $dataPersistor;

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

    /**
     * Save action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultRedirectFactory->create();
        $data = $this->getRequest()->getPostValue();
        if ($data) {
            $id = $this->getRequest()->getParam('question_id');
        
            $model = $this->_objectManager->create(\Magelearn\Categoryfaq\Model\Question::class)->load($id);
            if (!$model->getId() && $id) {
                $this->messageManager->addErrorMessage(__('This Question no longer exists.'));
                return $resultRedirect->setPath('*/*/');
            }
        
            $model->setData($data);
        
            try {
                $model->save();
                $this->messageManager->addSuccessMessage(__('You saved the Question.'));
                $this->dataPersistor->clear('magelearn_categoryfaq_question');
        
                if ($this->getRequest()->getParam('back')) {
                    return $resultRedirect->setPath('*/*/edit', ['question_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 Question.'));
            }
        
            $this->dataPersistor->set('magelearn_categoryfaq_question', $data);
            return $resultRedirect->setPath('*/*/edit', ['question_id' => $this->getRequest()->getParam('question_id')]);
        }
        return $resultRedirect->setPath('*/*/');
    }
}

We will add our DataProvider for Question model at Model/Question/DataProvider.php

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

namespace Magelearn\Categoryfaq\Model\Question;

use Magelearn\Categoryfaq\Model\ResourceModel\Question\CollectionFactory;
use Magento\Framework\App\Request\DataPersistorInterface;
use Magento\Ui\DataProvider\AbstractDataProvider;

class DataProvider extends AbstractDataProvider
{

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

    /**
     * @var array
     */
    protected $loadedData;
    /**
     * @inheritDoc
     */
    protected $collection;


    /**
     * @param string $name
     * @param string $primaryFieldName
     * @param string $requestFieldName
     * @param CollectionFactory $collectionFactory
     * @param DataPersistorInterface $dataPersistor
     * @param array $meta
     * @param array $data
     */
    public function __construct(
        $name,
        $primaryFieldName,
        $requestFieldName,
        CollectionFactory $collectionFactory,
        DataPersistorInterface $dataPersistor,
        array $meta = [],
        array $data = []
    ) {
        $this->collection = $collectionFactory->create();
        $this->dataPersistor = $dataPersistor;
        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->getId()] = $model->getData();
        }
        $data = $this->dataPersistor->get('magelearn_categoryfaq_question');
        
        if (!empty($data)) {
            $model = $this->collection->getNewEmptyItem();
            $model->setData($data);
            $this->loadedData[$model->getId()] = $model->getData();
            $this->dataPersistor->clear('magelearn_categoryfaq_question');
        }
        
        return $this->loadedData;
    }
}

Add Model/Category/Source/Categories.php file.

<?php

namespace Magelearn\Categoryfaq\Model\Category\Source;

use Magelearn\Categoryfaq\Model\ResourceModel\Category\CollectionFactory;

class Categories implements \Magento\Framework\Data\OptionSourceInterface
{
    /**
     * @var null|array
     */
    protected $options;

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

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

    /**
     * Return associative array with Category IDs and Names
     *
     * @return array|null
     */
    public function toOptionArray()
    {
        if (null == $this->options) {
            $this->options = $this->collectionFactory->create()
                ->addFieldToFilter('status', true)
                ->setOrder('sort_order','ASC')
                ->toOptionArray();
        }
        return $this->options;
    }
}

Now we will check our front end files to add new menu called `faq`.

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 etc/frontend/di.xml file to modify code for top menu items.

<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 controller file at Controller/Index/Index.php

<?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 highlighted code above we will add Plugin/Block/Topmenu.php file.

<?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
        ];
    }
}

Add layout file for front end at view/frontend/layout/faq_index_index.xml

<?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 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;
 
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;
    
    public function __construct(
        CollectionFactory $collectionFactory
        ) {
            $this->collectionFactory = $collectionFactory;
            parent::__construct();
    }
    
    /**
     * Get All Questions
     *
     * @return \Magento\Framework\DataObject[]
     */
    public function getItems()
    {
        $questionCollection = $this->collectionFactory->create();
        $questionCollection->addFieldToFilter('main_table.status', 1);
        $questionCollection->setOrder('categoryName', 'DESC');
        
        return $questionCollection->getItems();
    }
}

Add template file at view/frontend/templates/questions.phtml

<?php

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

/** @var Template $block */
$categoryName = false;
$viewModel = $block->getData('viewModel');
$items = $viewModel->getItems();
?>
<div class="container">
    <?php foreach ($items as $question): ?>
    <?php if ($question->getData('categoryName') !== $categoryName) : ?>
    <?php if ($categoryName != false): ?>
    <?php echo '</div>'; ?>
    <?php endif; ?>
    <div class="accordion">
    <?php $categoryName = $question->getData('categoryName'); ?>
    <?php if ($viewModel::MAIN_LABEL != $categoryName) : ?>
    <h2><?php echo $categoryName; ?></h2>
    <?php endif; ?>
    <?php endif; ?>
        <div class="accordion-item">
            <a><?php echo $question->getTitle(); ?></a>
            <div class="content">
                <?php echo $question->getAnswer(); ?>
            </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 highlighted in faq_index_index.xml file, we will add our css file at view/frontend/web/css/accordion.css

At last we will also check about GraphQL Implementation for this listing the FAQ data.

For that first create etc/schema.graphqls file.

type Query { 
    faq  : Faq @resolver( class: "Magelearn\\Categoryfaq\\Model\\Resolver\\Faq") @doc(description: "Query by Get FAQ List By Category.") 
}
type Faq @doc(description: "Faq defines the FAQ information") {
    allFaqs : [FaqRecord] @doc(description: "FAQ records with info")
}
type FaqRecord {
    id: Int @doc(description: "Get FAQ ID")
    title: String @doc(description: "Get FAQ title")
    answer: String @doc(description: "Get FAQ answer")
    status: Int @doc(description: "Get FAQ status")
    categoryName: String @doc(description: "Get FAQ Category")
}

As per highlighted above code, we will add resolver file at Model/Resolver/Faq.php

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

namespace Magelearn\Categoryfaq\Model\Resolver;

use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magelearn\Categoryfaq\Model\ResourceModel\Question\CollectionFactory;

class Faq implements ResolverInterface
{
    public function __construct(
        CollectionFactory $collectionFactory
        )
    {
        $this->collectionFactory = $collectionFactory;
    }
    
    /**
     * @inheritdoc
     */
    public function resolve(
        Field $field,
        $context,
        ResolveInfo $info,
        array $value = null,
        array $args = null
        ) {
            $faqData = $this->getFaqData();
            return $faqData;
    }
    /**
     * @return array
     * @throws GraphQlNoSuchEntityException
     */
    private function getFaqData(): array
    {
        try {
            /* filter for all the pages */
            $questionCollection = $this->collectionFactory->create();
            $questionCollection->addFieldToFilter('main_table.status', 1);
            $questionCollection->setOrder('categoryName', 'DESC');
            
            $faqItems = $questionCollection->getItems();
            
            $faqRecord['allFaqs'] = [];
            
            foreach($faqItems as $item) {
                $faqId = $item->getId();
                $faqRecord['allFaqs'][$faqId]['title'] = $item->getTitle();
                $faqRecord['allFaqs'][$faqId]['answer'] = html_entity_decode(nl2br($item->getAnswer()));
                $faqRecord['allFaqs'][$faqId]['status'] = $item->getStatus();
                $faqRecord['allFaqs'][$faqId]['categoryName'] = $item['categoryName'];
            }
        } catch (NoSuchEntityException $e) {
            throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e);
        }
        return $faqRecord;
    }
}

Now to check the GraphQL Query on chrome browser intall GraphQL Extension for Chrome.

Open the Extension and set Endpoint URL as http://baseurl/graphql

And In Query Variable add below Json Data:

{
  faq {
    allFaqs {
      id
      title
      answer
      status
      categoryName
    }
  }
}

It will display all the FAQs data in the result box.

0 Comments On "FAQ (frequently asked questions) By Category with jQuery Accordion Magento2 Module Including GraphQL"

Back To Top