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
<?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\\": "" } } }
<?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>
<?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>
<?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); }
<?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); }
<?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); }
<?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); }
<?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); }
<?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); }
<?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)); } }
<?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)); } }
<?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(); } }
<?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(); } }
<?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'); } }
<?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'); } }
<?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; } }
<?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; } }
<?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('*/*/'); } }
<?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 ]); } }
<?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"