Magento Admin Customizations
Jan 27, 2020 by Bhushan

Introduction:

Today Magento 2 is the biggest platform in the eCommerce field. Being a Web Developer I like to use Magento for creating e-commerce websites due to lots of features, options, and flexibility, Modules structure, its detailed and user-friendly admin interface Magento 2 has. Magento 2 uses nearly the Model, View, View-Model (MVVM) system. While creating a website/project using Magento 2, even it has lots of features but sometimes we need to create custom options due to the requirements of the project. These customizations can be for frontend or for admin.

In this article, we will learn how to customize the Magento 2 admin. We will do this by creating a custom module. Here we will not cover the basics of creating a custom module. We can learn this from the Magento 2 documentation.

In this custom module, we will learn how to add a custom menu in the Magento 2 admin and add a custom grid to this menu.

Let’s start.

Step 1: Add custom menu in Magento 2 admin.

First, create registration.php in app/code/Symphisys/Admin

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

Now create module.xml in app/code/Symphisys/Admin/etc

<?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="Symphisys_Admin" setup_version="1.0.0" />
</config>

Create menu.xml in app/code/Symphisys/Admin/etc/adminhtml to create a custom menu in admin.

<?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="Symphisys_Admin::level_one_menu" title="Symphisys" module="Symphisys_Admin" sortOrder="20" resource="Magento_Backend::content" />
		<add id="Symphisys_Admin::level_two_menu" title="Custom Admin" module="Symphisys_Admin" sortOrder="1" action="symph/grid/index" parent="Symphisys_Admin::level_one_menu" resource="Magento_Backend::content" />
		</menu>
		</config>

Now let’s know more about the attributes used above

id: is the identifier of a menu. This is unique and must follow the format like Vendor_ModuleName::menu_description.

title: title of the menu. which will be displayed in the admin sidebar menu.

module: name the module that we have created (Symphisys_Admin).

sortOrder: this attribute defined the position of the menu. Lower will display the menu at the top.

Resource: this attribute defined the ACL rule to identify which admin user can see this menu.

action: is used to link an admin URL. Format is like “router_name/controller_folder/action_name”

parent: this an id of another menu. By this attribute, Magento will know that this menu is a child of another menu.

We are done for now. Let’s see this module in action.

Open the SSH terminal and run the following commands.

php bin/magento module:enable Symphisys_Admin

php bin/magento setup:upgrade

php bin/magento setup:di:compile

php bin/magento indexer:reindex

php bin/magento cache:flush

Now login to your Magento 2 admin and see your module in action.

We learned how we can add our own menu in Magento 2 admin. Now moving towards bit complex customization by creating a grid.

First, create the directory structure which we will use for our custom module.
app/code/Symphisys/Admin
app/code/Symphisys/Admin/etc
app/code/Symphisys/Admin/etc/Adminhtml

Note: We already created the above directories during custom menu implementation.

app/code/Symphisys/Admin/Block/Adminhtml
app/code/Symphisys/Admin/Block/Adminhtml/Grid
app/code/Symphisys/Admin/Block/Adminhtml/Grid/Edit
app/code/Symphisys/Admin/Model
app/code/Symphisys/Admin/Model/ResourceModel
app/code/Symphisys/Admin/Model/ResourceModel/Grid
app/code/Symphisys/Admin/Setup
app/code/Symphisys/Admin/Controllers/Adminhtml
app/code/Symphisys/Admin/view/adminhtml/layout

We can divide the module into the following parts for a better understanding.
Part 1: Create the Installer file(database schema).
Part 2: Create admin routes
Part 3: Create Controllers
Part 4: Create Admin Grid using Component & Layout

Follow the following steps to create a few module files.

Step 1: Create the Installer file InstallSchema.php in app/code/Symphisys/Admin/Setup.
<?php
namespace Symphisys\Admin\Setup;
use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\App\Filesystem\DirectoryList;
/**
 * @codeCoverageIgnore
 */
class InstallSchema implements InstallSchemaInterface
{
	/**
	 * {@inheritdoc}
	 *
	 */
	public function install(
		SchemaSetupInterface $setup,
		ModuleContextInterface $context
	) {
		$installer = $setup;

		$installer->startSetup();
		/*
		 * Create table 'symphisys_grid_items'
		 */

		$table = $installer->getConnection()->newTable(
			$installer->getTable('symphisys_grid_items')
		)->addColumn(
			'entity_id',
			\Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
			null,
			['identity' => true, 'nullable' => false, 'primary' => true],
			'Item Id'
		)->addColumn(
			'title',
			\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
			255,
			['nullable' => false],
			'Title'
		)->addColumn(
			'content',
			\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
			'2M',
			['nullable' => false],
			'Post'
		)->addColumn(
			'publish_date',
			\Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
			null,
			[],
			'Publish Date'
		)->addColumn(
			'is_active',
			\Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT,
			null,
			[],
			'Active Status'
		)->addColumn(
			'created_at',
			\Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
			null,
			[
				'nullable' => false,
				'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT,
			],
			'Creation Time'
		)->addColumn(
			'update_time',
			\Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
			null,
			[],
			'Modification Time'
		)->setComment(
			'Data Table'
		);

		$installer->getConnection()->createTable($table);
		$installer->endSetup();
	}
}
Step 2: Now, create the Model file named Grid.php in app/code/Symphisys/Admin/Model.
<?php
namespace Symphisys\Admin\Model;
use Symphisys\Admin\Api\Data\GridInterface;
class Grid extends \Magento\Framework\Model\AbstractModel implements GridInterface
{
    /**
     * CMS page cache tag.
     */
    const CACHE_TAG = 'symphisys_grid_items';
    /**
     * @var string
     */
    protected $_cacheTag = 'symphisys_grid_items';
    /**
     * Prefix of model events names.
     *
     * @var string
     */
    protected $_eventPrefix = 'symphisys_grid_items';
    /**
     * Initialize resource model.
     */
    protected function _construct()
    {
        $this->_init('Symphisys\Admin\Model\ResourceModel\Grid');
    }
    /**
     * Get EntityId.
     *
     * @return int
     */
    public function getEntityId()
    {
        return $this->getData(self::ENTITY_ID);
    }
    /**
     * Set EntityId.
     */
    public function setEntityId($entityId)
    {
        return $this->setData(self::ENTITY_ID, $entityId);
    }
    /**
     * Get Title.
     *
     * @return varchar
     */
    public function getTitle()
    {
        return $this->getData(self::TITLE);
    }
    /**
     * Set Title.
     */
    public function setTitle($title)
    {
        return $this->setData(self::TITLE, $title);
    }
    /**
     * Get getContent.
     *
     * @return varchar
     */
    public function getContent()
    {
        return $this->getData(self::CONTENT);
    }
    /**
     * Set Content.
     */
    public function setContent($content)
    {
        return $this->setData(self::CONTENT, $content);
    }
    /**
     * Get PublishDate.
     *
     * @return varchar
     */
    public function getPublishDate()
    {
        return $this->getData(self::PUBLISH_DATE);
    }
    /**
     * Set PublishDate.
     */
    public function setPublishDate($publishDate)
    {
        return $this->setData(self::PUBLISH_DATE, $publishDate);
    }
    /**
     * Get IsActive.
     *
     * @return varchar
     */
    public function getIsActive()
    {
        return $this->getData(self::IS_ACTIVE);
    }
    /**
     * Set IsActive.
     */
    public function setIsActive($isActive)
    {
        return $this->setData(self::IS_ACTIVE, $isActive);
    }
    /**
     * Get UpdateTime.
     *
     * @return varchar
     */
    public function getUpdateTime()
    {
        return $this->getData(self::UPDATE_TIME);
    }
    /**
     * Set UpdateTime.
     */
    public function setUpdateTime($updateTime)
    {
        return $this->setData(self::UPDATE_TIME, $updateTime);
    }
    /**
     * Get CreatedAt.
     *
     * @return varchar
     */
    public function getCreatedAt()
    {
        return $this->getData(self::CREATED_AT);
    }
    /**
     * Set CreatedAt.
     */
    public function setCreatedAt($createdAt)
    {
        return $this->setData(self::CREATED_AT, $createdAt);
    }
}
Step 3: Now create Status.php in app/code/Symphisys/Admin/Model
<?php
namespace Symphisys\Admin\Model;
use Magento\Framework\Data\OptionSourceInterface;
class Status implements OptionSourceInterface
{
    /**
     * Get Grid row status type labels array.
     * @return array
     */
    public function getOptionArray()
    {
        $options = ['1' => __('Enabled'),'0' => __('Disabled')];
        return $options;
    }
    /**
     * Get Grid row status labels array with empty value for option element.
     *
     * @return array
     */
    public function getAllOptions()
    {
        $res = $this->getOptions();
        array_unshift($res, ['value' => '', 'label' => '']);
        return $res;
    }
    /**
     * Get Grid row type array for option element.
     * @return array
     */
    public function getOptions()
    {
        $res = [];
        foreach ($this->getOptionArray() as $index => $value) {
            $res[] = ['value' => $index, 'label' => $value];
        }
        return $res;
    }
    /**
     * {@inheritdoc}
     */
    public function toOptionArray()
    {
        return $this->getOptions();
    }
}
Step 4: Now, create the GridInterface.php in app/code/Symphisys/Admin/Api/Data
<?php
namespace Symphisys\Admin\Api\Data;
interface GridInterface
{
    /**
     * Constants for keys of data array. Identical to the name of the getter in snake case.
     */
    const ENTITY_ID = 'entity_id';
    const TITLE = 'title';
    const CONTENT = 'content';
    const PUBLISH_DATE = 'publish_date';
    const IS_ACTIVE = 'is_active';
    const UPDATE_TIME = 'update_time';
    const CREATED_AT = 'created_at';
    /**
     * Get EntityId.
     *
     * @return int
     */
    public function getEntityId();

    /**
     * Set EntityId.
     */
    public function setEntityId($entityId);

    /**
     * Get Title.
     *
     * @return varchar
     */
    public function getTitle();

    /**
     * Set Title.
     */
    public function setTitle($title);

    /**
     * Get Content.
     *
     * @return varchar
     */
    public function getContent();

    /**
     * Set Content.
     */
    public function setContent($content);

    /**
     * Get Publish Date.
     *
     * @return varchar
     */
    public function getPublishDate();

    /**
     * Set PublishDate.
     */
    public function setPublishDate($publishDate);

    /**
     * Get IsActive.
     *
     * @return varchar
     */
    public function getIsActive();

    /**
     * Set StartingPrice.
     */
    public function setIsActive($isActive);

    /**
     * Get UpdateTime.
     *
     * @return varchar
     */
    public function getUpdateTime();

    /**
     * Set UpdateTime.
     */
    public function setUpdateTime($updateTime);

    /**
     * Get CreatedAt.
     *
     * @return varchar
     */
    public function getCreatedAt();

    /**
     * Set CreatedAt.
     */
    public function setCreatedAt($createdAt);
}
Step 5: Now, create the Resource Model file named Grid.php in app/code/Symphisys/Admin/Model/ResourceModel. This file is for the table ‘symphisys_grid_items’
<?php

namespace Symphisys\Admin\Model\ResourceModel;

/**
 * Grid Grid mysql resource.
 */
class Grid extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
    /**
     * @var string
     */
    protected $_idFieldName = 'entity_id';
    /**
     * @var \Magento\Framework\Stdlib\DateTime\DateTime
     */
    protected $_date;

    /**
     * Construct.
     *
     * @param \Magento\Framework\Model\ResourceModel\Db\Context $context
     * @param \Magento\Framework\Stdlib\DateTime\DateTime       $date
     * @param string|null                                       $resourcePrefix
     */
    public function __construct(
        \Magento\Framework\Model\ResourceModel\Db\Context $context,
        \Magento\Framework\Stdlib\DateTime\DateTime $date,
        $resourcePrefix = null
    ) 
    {
        parent::__construct($context, $resourcePrefix);
        $this->_date = $date;
    }

    /**
     * Initialize resource model.
     */
    protected function _construct()
    {
        $this->_init('symphisys_grid_items', 'entity_id');
    }
}
Step 6: Now, create the Collection file named Collection.php in app/code/Symphisys/Admin/Model/ResourceModel/Grid. for table ‘symphisys_grid_items’
<?php

namespace Symphisys\Admin\Model\ResourceModel\Grid;

class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
{
    /**
     * @var string
     */
    protected $_idFieldName = 'entity_id';
    /**
     * Define resource model.
     */
    protected function _construct()
    {
        $this->_init('Symphisys\Admin\Model\Grid', 'Symphisys\Admin\Model\ResourceModel\Grid');
    }
}
Step 7: Now we will create ui_component for grid row. Create grid_record_grid_list.xml in app/code/Symphisys/Admin/view/adminhtml/ui_component directory.
<?xml version="1.0" encoding="UTF-8"?>
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Ui/etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">grid_record_grid_list.grid_record_grid_list_data_source</item>
            <item name="deps" xsi:type="string">grid_record_grid_list.grid_record_grid_list_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">grid_records_columns</item>
        <item name="buttons" xsi:type="array">
            <item name="add" xsi:type="array">
                <item name="name" xsi:type="string">add</item>
                <item name="label" xsi:type="string" translate="true">Add New</item>
                <item name="class" xsi:type="string">primary</item>
                <item name="url" xsi:type="string">*/*/addrow</item>
            </item>
        </item>
    </argument>
    <dataSource name="grid_record_grid_list_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider</argument>
            <argument name="name" xsi:type="string">grid_record_grid_list_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">entity_id</argument>
            <argument name="requestFieldName" xsi:type="string">entity_id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="update_url" xsi:type="url" path="mui/index/render"/>
                </item>
            </argument>
        </argument>
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
            </item>
        </argument>
    </dataSource>
    <container name="listing_top">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="template" xsi:type="string">ui/grid/toolbar</item>
                <item name="stickyTmpl" xsi:type="string">ui/grid/sticky/toolbar</item>
            </item>
        </argument>
        <bookmark name="bookmarks"/>
        <columnsControls name="columns_controls"/>
        <filters name="listing_filters">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="columnsProvider" xsi:type="string">grid_record_grid_list.grid_record_grid_list.grid_records_columns</item>
                    <item name="storageConfig" xsi:type="array">
                        <item name="provider" xsi:type="string">grid_record_grid_list.grid_record_grid_list.listing_top.bookmarks</item>
                        <item name="namespace" xsi:type="string">current.filters</item>
                    </item>
                    <item name="templates" xsi:type="array">​_
                        <item name="filters" xsi:type="array">
                            <item name="select" xsi:type="array">
                                <item name="component" xsi:type="string">Magento_Ui/js/form/element/ui-select</item>
                                <item name="template" xsi:type="string">ui/grid/filters/elements/ui-select</item>
                            </item>
                        </item>
                    </item>
                    <item name="childDefaults" xsi:type="array">
                        <item name="provider" xsi:type="string">grid_record_grid_list.grid_record_grid_list.listing_top.listing_filters</item>
                        <item name="imports" xsi:type="array">
                            <item name="visible" xsi:type="string">grid_record_grid_list.grid_record_grid_list.grid_records_columns.${ $.index }:visible</item>
                        </item>
                    </item>
                </item>
                <item name="observers" xsi:type="array">
                    <item name="column" xsi:type="string">column</item>
                </item>
            </argument>
        </filters>
        <massaction name="listing_massaction">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="selectProvider" xsi:type="string">grid_record_grid_list.grid_record_grid_list.grid_records_columns.ids</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/tree-massactions</item>
                    <item name="indexField" xsi:type="string">entity_id</item>
                </item>
            </argument>
            <!-- Mass actions which you want to add in your grid-->
            <action name="delete">
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="type" xsi:type="string">delete</item>
                        <item name="label" xsi:type="string" translate="true">Delete</item>
                        <item name="url" xsi:type="url" path="grid/grid/massdelete"/>
                        <item name="confirm" xsi:type="array">
                            <item name="title" xsi:type="string" translate="true">Delete</item>
                            <item name="message" xsi:type="string" translate="true">Do you want to delete selected row record?</item>
                        </item>
                    </item>
                </argument>
            </action>
        </massaction>
        <paging name="listing_paging">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="storageConfig" xsi:type="array">
                        <item name="provider" xsi:type="string">grid_record_grid_list.grid_record_grid_list.listing_top.bookmarks</item>
                        <item name="namespace" xsi:type="string">current.paging</item>
                    </item>
                    <item name="selectProvider" xsi:type="string">grid_record_grid_list.grid_record_grid_list.grid_records_columns.ids</item>
                </item>
            </argument>
        </paging>
    </container>
    <columns name="grid_records_columns">
        <selectionsColumn name="ids">
           <argument name="data" xsi:type="array">
               <item name="config" xsi:type="array">
                   <item name="indexField" xsi:type="string">entity_id</item>
                   <item name="sorting" xsi:type="string">desc</item>
                   <item name="sortOrder" xsi:type="number">0</item>
               </item>
           </argument>
       </selectionsColumn>
       <column name="title">
           <argument name="data" xsi:type="array">
               <item name="config" xsi:type="array">
                   <item name="filter" xsi:type="string">textRange</item>
                   <item name="label" xsi:type="string" translate="true">Title</item>
               </item>
           </argument>
       </column>
       <column name="content" >
           <argument name="data" xsi:type="array">
               <item name="config" xsi:type="array">
                   <item name="filter" xsi:type="string">false</item>
                   <item name="label" xsi:type="string" translate="true">Content</item>
               </item>
           </argument>
       </column>
       <column name="is_active" >
           <argument name="data" xsi:type="array">
               <item name="options" xsi:type="object">Symphisys\Admin\Model\Status</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="label" xsi:type="string" translate="true">Is Active</item>
               </item>
           </argument>
       </column>
       <column name="publish_date" class="Magento\Ui\Component\Listing\Columns\Date" >
           <argument name="data" xsi:type="array">
               <item name="config" xsi:type="array">
                   <item name="filter" xsi:type="string">dateRange</item>
                   <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/date</item>
                   <item name="dataType" xsi:type="string">date</item>
                   <item name="label" xsi:type="string" translate="true">Publish Date</item>
               </item>
           </argument>
       </column>
       <column name="update_time" class="Magento\Ui\Component\Listing\Columns\Date" >
           <argument name="data" xsi:type="array">
               <item name="config" xsi:type="array">
                   <item name="filter" xsi:type="string">dateRange</item>
                   <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/date</item>
                   <item name="dataType" xsi:type="string">date</item>
                   <item name="label" xsi:type="string" translate="true">Update Time</item>
               </item>
           </argument>
       </column>
       <column name="created_at" class="Magento\Ui\Component\Listing\Columns\Date" >
           <argument name="data" xsi:type="array">
               <item name="config" xsi:type="array">
                   <item name="filter" xsi:type="string">dateRange</item>
                   <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/date</item>
                   <item name="dataType" xsi:type="string">date</item>
                   <item name="label" xsi:type="string" translate="true">Created At</item>
               </item>
           </argument>
       </column>
       <!-- Add Action with each row of grid and for this we will create a class Action -->
       <actionsColumn name="actions" class="Symphisys\Admin\Ui\Component\Listing\Grid\Column\Action">
           <argument name="data" xsi:type="array">
               <item name="config" xsi:type="array">
                   <item name="resizeEnabled" xsi:type="boolean">false</item>
                   <item name="resizeDefaultWidth" xsi:type="string">107</item>
                   <item name="indexField" xsi:type="string">entity_id</item>
               </item>
           </argument>
       </actionsColumn>
    </columns>
</listing>
Step 8: Now, create di.xml in app/code/Symphisys/Admin/etc file to map data provider with the model collection.
<?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="Symphisys\Admin\Api\Data\GridInterface" type="Symphisys\Admin\Model\Grid" />

    <virtualType name="Symphisys\Admin\Model\ResourceModel\Grid\Grid\Collection" type="Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult">
        <arguments>
            <argument name="mainTable" xsi:type="string">symphisys_grid_items</argument>
            <argument name="resourceModel" xsi:type="string">Symphisys\Admin\Model\ResourceModel\Grid</argument>
        </arguments>
    </virtualType>
    <type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
        <arguments>
            <argument name="collections" xsi:type="array">
                <item name="grid_record_grid_list_data_source" xsi:type="string">Symphisys\Admin\Model\ResourceModel\Grid\Grid\Collection</item>
            </argument>
        </arguments>
    </type>
</config>
Step 9: Now we will create Action Class Action.php in app/code/Symphisys/Admin/Ui/Component/Listing/Grid/Column
<?php

namespace Symphisys\Admin\Ui\Component\Listing\Grid\Column;

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

class Action extends Column
{
    /** Url path */
    const ROW_EDIT_URL = 'symph/grid/addrow';
    /** @var UrlInterface */
    protected $_urlBuilder;

    /**
     * @var string
     */
    private $_editUrl;

    /**
     * @param ContextInterface   $context
     * @param UiComponentFactory $uiComponentFactory
     * @param UrlInterface       $urlBuilder
     * @param array              $components
     * @param array              $data
     * @param string             $editUrl
     */
    public function __construct(
        ContextInterface $context,
        UiComponentFactory $uiComponentFactory,
        UrlInterface $urlBuilder,
        array $components = [],
        array $data = [],
        $editUrl = self::ROW_EDIT_URL
    ) 
    {
        $this->_urlBuilder = $urlBuilder;
        $this->_editUrl = $editUrl;
        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) {
                $name = $this->getData('name');
                if (isset($item['entity_id'])) {
                    $item[$name]['edit'] = [
                        'href' => $this->_urlBuilder->getUrl(
                            $this->_editUrl, 
                            ['id' => $item['entity_id']]
                        ),
                        'label' => __('Edit'),
                    ];
                }
            }
        }

        return $dataSource;
    }
}

Now UI component and data provider are ready. Its time to create controllers.

Step 10: Now create controller file Index.php in app/code/Symphisys/Admin/Controller/Adminhtml/Grid
<?php

namespace Symphisys\Admin\Controller\Adminhtml\Grid;

class Index extends \Magento\Backend\App\Action
{
    /**
     * @var \Magento\Framework\View\Result\PageFactory
     */
    protected $_resultPageFactory;

    /**
     * @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
    ) 
    {
        parent::__construct($context);
        $this->_resultPageFactory = $resultPageFactory;
    }

    /**
     * Grid List page.
     *
     * @return \Magento\Backend\Model\View\Result\Page
     */
    public function execute()
    {
        /** @var \Magento\Backend\Model\View\Result\Page $resultPage */
        $resultPage = $this->_resultPageFactory->create();
        $resultPage->setActiveMenu('Symphisys_Admin::level_one_menu');
        $resultPage->getConfig()->getTitle()->prepend(__('Grid List'));

        return $resultPage;
    }

    /**
     * Check Grid List Permission.
     *
     * @return bool
     */
    protected function _isAllowed()
    {
        return $this->_authorization->isAllowed('Symphisys_Admin::level_one_menu');
    }
}
Step 11: Create route file routes.xml in app/code/Symphisys/Admin/etc/adminhtml
<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="admin">
        <route id="grid" frontName="symph">
            <module name="Symphisys_Admin" />
        </route>
    </router>
</config>
Step 12: And for grid display create layout file grid_grid_index.xml in app/code/Symphisys/Admin/view/adminhtml/layout
<?xml version="1.0"?>

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-1column"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
           <block class="Symphisys\Admin\Block\Adminhtml\Grid\AddRow" name="add_row" />
        </referenceContainer>
    </body>
</page>

Now our grid ready in the admin section as follow.

Now we’ll learn how to save/edit data in the table using the form. In the Ui component file grid_record_grid_list.xml, we already added the path of add form URL.

Step 13: Now we’ll create controller file AddRow.php in app/code/Symphisys/Admin/Controller/Adminhtml/Grid
<?php

namespace Symphisys\Admin\Controller\Adminhtml\Grid;

use Magento\Framework\Controller\ResultFactory;

class AddRow extends \Magento\Backend\App\Action
{
    /**
     * @var \Magento\Framework\Registry
     */
    private $coreRegistry;

    
    private $gridFactory;

    /**
     * @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,
        \Symphisys\Admin\Model\GridFactory $gridFactory
    ) {
        parent::__construct($context);
        $this->coreRegistry = $coreRegistry;
        $this->gridFactory = $gridFactory;
    }

    /**
     * Mapped Grid List page.
     * @return \Magento\Backend\Model\View\Result\Page
     */
    public function execute()
    {
        $rowId = (int) $this->getRequest()->getParam('id'); var_dump($rowId);
        $rowData = $this->gridFactory->create();
        /** @var \Magento\Backend\Model\View\Result\Page $resultPage */
        if ($rowId) {
           $rowData = $rowData->load($rowId);
           $rowTitle = $rowData->getTitle();
           if (!$rowData->getEntityId()) {
               $this->messageManager->addError(__('row data no longer exist.'));
               $this->_redirect('symph/grid/index');
               return;
           }
       }
       var_dump($rowId);
       $this->coreRegistry->register('row_data', $rowData);
       $resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE);
       $title = $rowId ? __('Edit Row Data ').$rowTitle : __('Add Row Data');
       $resultPage->getConfig()->getTitle()->prepend($title);
       return $resultPage;
    }

    protected function _isAllowed()
    {
        return $this->_authorization->isAllowed('Symphisys_Admin::add_row');
    }
}
Step 14: Now, create block file AddRow.php in app/code/Symphisys/Admin/Block/Adminhtml/Grid
<?php

namespace Symphisys\Admin\Block\Adminhtml\Grid;

class AddRow extends \Magento\Backend\Block\Widget\Form\Container
{
    /**
     * Core registry.
     *
     * @var \Magento\Framework\Registry
     */
    protected $_coreRegistry = null;

    /**
     * @param \Magento\Backend\Block\Widget\Context $context
     * @param \Magento\Framework\Registry           $registry
     * @param array                                 $data
     */
    public function __construct(
        \Magento\Backend\Block\Widget\Context $context,
        \Magento\Framework\Registry $registry,
        array $data = []
    ) 
    {
        $this->_coreRegistry = $registry;
        parent::__construct($context, $data);
    }

    /**
     * Initialize Imagegallery Images Edit Block.
     */
    protected function _construct()
    {
        $this->_objectId = 'row_id';
        $this->_blockGroup = 'Symphisys_Admin';
        $this->_controller = 'adminhtml_grid';
        parent::_construct();
        if ($this->_isAllowedAction('Symphisys_Admin::add_row')) {
            $this->buttonList->update('save', 'label', __('Save'));
        } else {
            $this->buttonList->remove('save');
        }
        $this->buttonList->remove('reset');
    }

    /**
     * Retrieve text for header element depending on loaded image.
     *
     * @return \Magento\Framework\Phrase
     */
    public function getHeaderText()
    {
        return __('Add RoW Data');
    }

    /**
     * Check permission for passed action.
     *
     * @param string $resourceId
     *
     * @return bool
     */
    protected function _isAllowedAction($resourceId)
    {
        return $this->_authorization->isAllowed($resourceId);
    }

    /**
     * Get form action URL.
     *
     * @return string
     */
    public function getFormActionUrl()
    {
        if ($this->hasFormActionUrl()) {
            return $this->getData('form_action_url');
        }

        return $this->getUrl('*/*/save');
    }
}
Step 15: Now create block file Form.php in app/code/Symphisys/Admin/Block/Adminhtml/Grid/Edit. This file will create fields in Add/Edit actions.
<?php

namespace Symphisys\Admin\Block\Adminhtml\Grid\Edit;


/**
 * Adminhtml Add New Row Form.
 */
class Form extends \Magento\Backend\Block\Widget\Form\Generic
{
    /**
     * @var \Magento\Store\Model\System\Store
     */
    protected $_systemStore;

    /**
     * @param \Magento\Backend\Block\Template\Context $context
     * @param \Magento\Framework\Registry             $registry
     * @param \Magento\Framework\Data\FormFactory     $formFactory
     * @param array                                   $data
     */
    public function __construct(
        \Magento\Backend\Block\Template\Context $context,
        \Magento\Framework\Registry $registry,
        \Magento\Framework\Data\FormFactory $formFactory,
        \Magento\Cms\Model\Wysiwyg\Config $wysiwygConfig,
        \Symphisys\Admin\Model\Status $options,
        array $data = []
    ) 
    {
        $this->_options = $options;
        $this->_wysiwygConfig = $wysiwygConfig;
        parent::__construct($context, $registry, $formFactory, $data);
    }

    /**
     * Prepare form.
     *
     * @return $this
     */
    protected function _prepareForm()
    {
        $dateFormat = $this->_localeDate->getDateFormat(\IntlDateFormatter::SHORT);
        $model = $this->_coreRegistry->registry('row_data');
        $form = $this->_formFactory->create(
            ['data' => [
                            'id' => 'edit_form', 
                            'enctype' => 'multipart/form-data', 
                            'action' => $this->getData('action'), 
                            'method' => 'post'
                        ]
            ]
        );

        $form->setHtmlIdPrefix('wkgrid_');
        if ($model->getEntityId()) {
            $fieldset = $form->addFieldset(
                'base_fieldset',
                ['legend' => __('Edit Row Data'), 'class' => 'fieldset-wide']
            );
            $fieldset->addField('entity_id', 'hidden', ['name' => 'entity_id']);
        } else {
            $fieldset = $form->addFieldset(
                'base_fieldset',
                ['legend' => __('Add Row Data'), 'class' => 'fieldset-wide']
            );
        }

        $fieldset->addField(
            'title',
            'text',
            [
                'name' => 'title',
                'label' => __('Title'),
                'id' => 'title',
                'title' => __('Title'),
                'class' => 'required-entry',
                'required' => true,
            ]
        );

        $wysiwygConfig = $this->_wysiwygConfig->getConfig(['tab_id' => $this->getTabId()]);

        $fieldset->addField(
            'content',
            'editor',
            [
                'name' => 'content',
                'label' => __('Content'),
                'style' => 'height:36em;',
                'required' => true,
                'config' => $wysiwygConfig
            ]
        );

        $fieldset->addField(
            'publish_date',
            'date',
            [
                'name' => 'publish_date',
                'label' => __('Publish Date'),
                'date_format' => $dateFormat,
                'time_format' => 'HH:mm:ss',
                'class' => 'validate-date validate-date-range date-range-custom_theme-from',
                'class' => 'required-entry',
                'style' => 'width:200px',
            ]
        );
        $fieldset->addField(
            'is_active',
            'select',
            [
                'name' => 'is_active',
                'label' => __('Status'),
                'id' => 'is_active',
                'title' => __('Status'),
                'values' => $this->_options->getOptionArray(),
                'class' => 'status',
                'required' => true,
            ]
        );
        $form->setValues($model->getData());
        $form->setUseContainer(true);
        $this->setForm($form);

        return parent::_prepareForm();
    }
}
Step 16: To render this form we have to create layout file grid_grid_addrow.xml in app/code/Symphisys/Admin/view/adminhtml/layout.
<?xml version="1.0"?>

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-1column"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
           <block class="Symphisys\Admin\Block\Adminhtml\Grid\AddRow" name="add_row" />
        </referenceContainer>
    </body>
</page>
Step 17: Now create controller file Save.php in app/code/Symphisys/Admin/Controller/Adminhtml/Grid to save the record.
<?php

namespace Symphisys\Admin\Controller\Adminhtml\Grid;

class Save extends \Magento\Backend\App\Action
{
    
    var $gridFactory;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Symphisys\Admin\Model\GridFactory $gridFactory
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Symphisys\Admin\Model\GridFactory $gridFactory
    ) {
        parent::__construct($context);
        $this->gridFactory = $gridFactory;
    }

    /**
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     * @SuppressWarnings(PHPMD.NPathComplexity)
     */
    public function execute()
    {
        $data = $this->getRequest()->getPostValue();
        if (!$data) {
            $this->_redirect('grid/grid/addrow');
            return;
        }
        try {
            $rowData = $this->gridFactory->create();
            $rowData->setData($data);
            if (isset($data['id'])) {
                $rowData->setEntityId($data['id']);
            }
            $rowData->save();
            $this->messageManager->addSuccess(__('Row data has been successfully saved.'));
        } catch (\Exception $e) {
            $this->messageManager->addError(__($e->getMessage()));
        }
        $this->_redirect('grid/grid/index');
    }

    /**
     * @return bool
     */
    protected function _isAllowed()
    {
        return $this->_authorization->isAllowed('Symphisys_Admin::save');
    }
}

After saving row data it will display in the grid as follow

Step 18: Now create controller file MassDelete.php in app/code/Symphisys/Admin/Controller/Adminhtml/Grid
<?php

namespace Symphisys\Admin\Controller\Adminhtml\Grid;

use Magento\Framework\Controller\ResultFactory;
use Magento\Backend\App\Action\Context;
use Magento\Ui\Component\MassAction\Filter;
use Symphisys\Admin\Model\ResourceModel\Grid\CollectionFactory;

class MassDelete extends \Magento\Backend\App\Action
{
    /**
     * Massactions filter.​_
     * @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
    ) {

        $this->_filter = $filter;
        $this->_collectionFactory = $collectionFactory;
        parent::__construct($context);
    }

    /**
     * @return \Magento\Backend\Model\View\Result\Redirect
     */
    public function execute()
    {
        $collection = $this->_filter->getCollection($this->_collectionFactory->create());
        $recordDeleted = 0;
        foreach ($collection->getItems() as $record) {
            $record->setId($record->getEntityId());
            $record->delete();
            $recordDeleted++;
        }
        $this->messageManager->addSuccess(__('%1 item(s) have been deleted.', $recordDeleted));

        return $this->resultFactory->create(ResultFactory::TYPE_REDIRECT)->setPath('*/*/index');
    }

    /**
     * Check Category Map recode delete Permission.
     * @return bool
     */
    protected function _isAllowed()
    {
        return $this->_authorization->isAllowed('Symphisys_Admin::row_data_delete');
    }
}

We are all done. Our Grid is ready for Add/Edit/Delete actions. Enjoy!!!