Magento2 | PWA | GraphQL

Magento2 Save Contactus Data Into Databse and display in admin grid


In this post we wiil check how to save contactus form data into Magento2 admin by creating admin grid.


Here, i will cover all point about how to make admin grid with UI_Component in Magento2.

Let's start it by creating custom extension. 

Find Complete Module on Github

Create folder inside app/code/Magelearn/Contactus

Add registration.php file in it:
<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
use Magento\Framework\Component\ComponentRegistrar;
 
ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magelearn_Contactus', __DIR__);
Add composer.json file in it:
{
    "name": "magelearn/module-Contactus",
    "description": "Magento2 Save Contactus data in database and display it in backend admin grid.",
    "type": "magento2-module",
    "license": "OSL-3.0",
    "authors": [
        {
            "email": "vijaymrami@gmail.com",
            "name": "vijay rami"
        }
    ],
    "minimum-stability": "dev",
    "require": {},
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Magelearn\\Contactus\\": ""
        }
    }
}
Add etc/module.xml file in it:
Here we will add our setup_version number to 1.0.1
To make our upgrade schema script that will be used later on.
<?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_Contactus" setup_version="1.0.1">
    	<sequence>
		   <module name="Magento_Backend"/>
		   <module name="Magento_Ui"/>
		</sequence>
    </module>
</config>
Now we will first override Contact us Post method. So, that after submitting contactus form it will save contact form data into database.

But to save data into database we have to create database table.
So, first let's start by creating a setup script.

Create InstallSchema.php file in Setup folder.
<?php

namespace Magelearn\Contactus\Setup;

use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\DB\Ddl\Table;

class InstallSchema implements InstallSchemaInterface
{
    public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $installer = $setup;
        $installer->startSetup();
        // Get magelearn_contactus table
        $tableName = $installer->getTable('magelearn_contactus');
        // Check if the table already exists
        if ($installer->getConnection()->isTableExists($tableName) != true) {
            // Create magelearn_contactus table
            $table = $installer->getConnection()
                ->newTable($tableName)
                ->addColumn(
                    'id',
                    Table::TYPE_INTEGER,
                    null,
                    [
                        'identity' => true,
                        'unsigned' => true,
                        'nullable' => false,
                        'primary' => true
                    ],
                    'ID'
                )
                ->addColumn(
                    'name',
                    Table::TYPE_TEXT,
                    null,
                    ['nullable' => false, 'default' => ''],
                    'Name'
                )
                ->addColumn(
                    'email',
                    Table::TYPE_TEXT,
                    null,
                    ['nullable' => false, 'default' => ''],
                    'Email'
                )
                ->addColumn(
                    'telephone',
                    Table::TYPE_TEXT,
                    null,
                    ['nullable' => false, 'default' => ''],
                    'Telephone'
                )
                ->addColumn(
                    'comment',
                    Table::TYPE_TEXT,
                    null,
                    ['nullable' => false, 'default' => ''],
                    'Comment'
                )
                ->addColumn(
                    'created_at',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
                    null,
                    [
                        'nullable' => false, 
                        'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT
                    ],
                    'Created At'
                )
                ->setComment('Contactus Table')
                ->setOption('type', 'InnoDB')
                ->setOption('charset', 'utf8');
            $installer->getConnection()->createTable($table);
        }

        $installer->endSetup();
    }
}
When we create admin grid at that time to make filter searchbar fully workable, need to add indexes for the table fields.

For that we will create UpgradeSchema.php file inside Setup folder.
<?php

namespace Magelearn\Contactus\Setup;

use Magento\Framework\Setup\UpgradeSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;

class UpgradeSchema implements UpgradeSchemaInterface
{
   
    public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
		$installer = $setup;
        $installer->startSetup();

		if(version_compare($context->getVersion(), '1.0.1', '<')) {
		$table = $setup->getTable('magelearn_contactus');

        $setup->getConnection()
            ->addIndex(
                $table,
                $setup->getIdxName(
                    $table,
                    ['name', 'email', 'telephone', 'comment'],
                    \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_FULLTEXT
                ),
                ['name', 'email', 'telephone', 'comment'],
                \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_FULLTEXT
            );
		}
	}
}
Now we will override contactus post method and modify code to save data into database.

Create di.xml file inside etc folder.
Here we override contact post method with plugin.
And also add all major nodes for UI_Component for admin grid. We will discuss later on that.
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">   
    <type name="Magento\Contact\Controller\Index\Post">
        <plugin name="magelearn-contact-controller" type="Magelearn\Contactus\Plugin\ContactusPlugin" sortOrder="3"/>
    </type>
    <virtualType name="MagelearncontactusGirdFilterPool" type="Magento\Framework\View\Element\UiComponent\DataProvider\FilterPool">
        <arguments>
            <argument name="appliers" xsi:type="array">
                <item name="regular" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\RegularFilter</item>
                <item name="fulltext" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\FulltextFilter</item>
            </argument>
        </arguments>
    </virtualType>
    <virtualType name="MagelearncontactusGridDataProvider" type="Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider">
        <arguments>
            <argument name="collection" xsi:type="object" shared="false">Magelearn\Contactus\Model\ResourceModel\Contactus\Collection</argument>
            <argument name="filterPool" xsi:type="object" shared="false">MagelearncontactusGirdFilterPool</argument>
        </arguments>
    </virtualType>
    <type name="Magelearn\Contactus\Model\ResourceModel\Contactus\Grid\Collection">
        <arguments>
            <argument name="mainTable" xsi:type="string">magelearn_contactus</argument>
            <argument name="eventPrefix" xsi:type="string">magelearn_contactus_grid_collection</argument>
            <argument name="eventObject" xsi:type="string">magelearn_contactus_grid_collection</argument>
            <argument name="resourceModel" xsi:type="string">Magelearn\Contactus\Model\ResourceModel\Contactus</argument>
        </arguments>
    </type>
    <type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
        <arguments>
            <argument name="collections" xsi:type="array">
                <item name="mladmincontactus_contactus_listing_data_source" xsi:type="string">Magelearn\Contactus\Model\ResourceModel\Contactus\Grid\Collection</item>                
            </argument>
        </arguments>
    </type>
</config>

Create ContactusPlugin.php file inside Plugin folder.

<?php

namespace Magelearn\Contactus\Plugin;

/**
 * Class ContactusPlugin
 * @package Magelearn\Contactus\Plugin
 */

class ContactusPlugin
{    
    /**
     * @var \Magelearn\Contactus\Model\Contactus
     */
    protected $contactus;
 
    /**
     * Plugin constructor.
     *
     * @param \Magelearn\Contactus\Model\Contactus $contactus
     */
    public function __construct(
        \Magelearn\Contactus\Model\Contactus $contactus       
    ) {
        $this->contactus = $contactus;        
    }

    public function aroundExecute(\Magento\Contact\Controller\Index\Post $subject, \Closure $proceed)
    {   
        $request = $subject->getRequest()->getPostValue();
        if ($this->validatedParams($request)) {
            $this->contactus->setData($request); 
            $this->contactus->save();
        }
        $returnValue = $proceed();
        return $returnValue;
    }

    /**
     * @return array
     * @throws \Exception
     */
    private function validatedParams($request)
    {
        if (trim($request['name']) === '') {
            throw new LocalizedException(__('Enter the Name and try again.'));
        }
        if (trim($request['comment']) === '') {
            throw new LocalizedException(__('Enter the comment and try again.'));
        }
        if (false === \strpos($request['email'], '@')) {
            throw new LocalizedException(__('The email address is invalid. Verify the email address and try again.'));
        }
        if (trim($request['hideit']) !== '') {
            throw new \Exception();
        }
        return $request;
    }
}

Here with setData() method we set data into the model object and with save() method we save our contactus data by calling our model.
Now, let's create our custom model, resource model and collection file.

First create file Contactus.php inside Model folder
From this file, we will give our ResourceModel file path.

<?php

namespace Magelearn\Contactus\Model;

/**
 * Class Contactus
 * @package Magelearn\Contactus\Model
 */

class Contactus extends \Magento\Framework\Model\AbstractModel
{       
    protected function _construct()
    {
        $this->_init('Magelearn\Contactus\Model\ResourceModel\Contactus');
    }
}

Now Create Contactus.php inside Model/ResourceModel folder.

Here we define our custom table name and its primary key.

<?php

namespace Magelearn\Contactus\Model\ResourceModel;

/**
 * Class Contactus
 * @package Magelearn\Contactus\Model\ResourceModel
 */

class Contactus extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
    protected function _construct()
    {
        $this->_init('magelearn_contactus', 'id');
    }
}

Now, create Collection.php file inside Model/ResourceModel/Contactus

Here, we initialize our Model and ResourceModel and get collection.

<?php

namespace Magelearn\Contactus\Model\ResourceModel\Contactus;

use \Magelearn\Contactus\Model\ResourceModel\AbstractCollection;

/**
 * Class Collection
 * @package Magelearn\Contactus\Model\ResourceModel\Contactus
 */

class Collection extends AbstractCollection
{
    protected $_idFieldName = 'id';
    protected $_previewFlag;
    protected function _construct()
    {
        $this->_init(
            'Magelearn\Contactus\Model\Contactus',
            'Magelearn\Contactus\Model\ResourceModel\Contactus'
        );
        $this->_map['fields']['id'] = 'main_table.id';
    }
}

In Collection.php file inside we have used our custom AbstractCollection file.

For that we will create AbstractCollection.php file inside Model/ResourceModel

<?php

namespace Magelearn\Contactus\Model\ResourceModel;

/**
 * Class AbstractCollection
 * @package Magelearn\Contactus\Model\ResourceModel
 */

abstract class AbstractCollection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
{

    public function __construct(
        \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory,
        \Psr\Log\LoggerInterface $logger,
        \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy,
        \Magento\Framework\Event\ManagerInterface $eventManager,
        \Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
        \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null
    ) {
        parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $connection, $resource);
    }
    public function addFieldToFilter($field, $condition = null)
    {
        if ($field === 'store_id') {
            return $this->addStoreFilter($condition, false);
        }

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

Now our Model, ResourceModel and Collecion files are ready.
we will move forward to create admin menu.

First create menu.xml file inside etc/adminhtml folder

<?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_Contactus::base" title="Magelearn" module="Magelearn_Contactus" sortOrder="30" resource="Magelearn_Contactus::contactus"/>
        <add id="Magelearn_Contactus::manage_mlcontactus" title="Manage Contactus" module="Magelearn_Contactus" sortOrder="45" parent="Magelearn_Contactus::base" action="mladmincontactus/contactus" resource="Magelearn_Contactus::manage_mlcontactus"/>
    </menu>
</config>

Here we add seprate parent menu with tile Magelearn
And inside it we call mladmincontactus/contactus action url

Now, create routes.xml file inside etc/adminhtml folder.

<?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="mladmincontactus" frontName="mladmincontactus">
            <module name="Magelearn_Contactus" before="Magento_Backend" />
        </route>
    </router>
</config>

To Call this admin menu url as routename/controllername/actionname, we need to create controller and layout files.

Before Procees further we will create acl.xml file inside etc folder.

<?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_Contactus::base" title="Magelearn">
                    <resource id="Magelearn_Contactus::contactus" title="Contactus" sortOrder="30"/>
                </resource>
            </resource>
        </resources>
    </acl>
</config>

Default Index controller is called. So, Create Index.php file inside Controller/Adminhtml/Contactus folder.

<?php

namespace Magelearn\Contactus\Controller\Adminhtml\Contactus;

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

/**
 * Class Index
 * @package Magelearn\Contactus\Controller\Adminhtml\Contactus
 */

class Index extends \Magento\Backend\App\Action
{
    /**
     * @var PageFactory
     */
    protected $resultPageFactory;

    /**
     * @param Context $context
     * @param PageFactory $resultPageFactory
     */
    public function __construct(
        Context $context,
        PageFactory $resultPageFactory
    ) {
        parent::__construct($context);
        $this->resultPageFactory = $resultPageFactory;
    }
    /**
     * Index action
     *
     * @return \Magento\Backend\Model\View\Result\Page
     */
    public function execute()
    {
        $resultPage = $this->resultPageFactory->create();
        $resultPage->setActiveMenu('Magelearn_Contactus::base');
        $resultPage->addBreadcrumb(__('Contactus'), __('Contactus'));
        $resultPage->addBreadcrumb(__('Manage Contactus'), __('Manage Contactus'));
        $resultPage->getConfig()->getTitle()->prepend(__('Manage Contactus'));
        return $resultPage;
    }
}

Now add layout file mladmincontactus_contactus_index.xml inside view/adminhtml/layout folder.

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

Here it is told to get uiComponent and put it in the container "content" of our theme. We will give it the name "mladmincontactus_contactus_listing". This magento2 uiComponent is as you can imagine our Grid.

So we will define our uiComponent.

Create mladmincontactus_contactus_listing.xml inside view/adminhtml/ui_component folder.

<?xml version="1.0" encoding="UTF-8"?>
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">

    <!-- main part of the grid -->
    <argument name="data" xsi:type="array">
        <!-- define where to find the date source -->
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">mladmincontactus_contactus_listing.mladmincontactus_contactus_listing_data_source</item>
            <item name="deps" xsi:type="string">mladmincontactus_contactus_listing.mladmincontactus_contactus_listing_data_source</item>
        </item>
        <!-- define where to find the columns -->
        <item name="spinner" xsi:type="string">mladmincontactus_contactus_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">*/*/new</item>
            </item>
        </item>
    </argument>
    
      <!-- define the date source (must be the same than in argument/item/provider and argument/js_config/deps -->
    <dataSource name="mladmincontactus_contactus_listing_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <!-- unique name for the grid -->
            <argument name="class" xsi:type="string">MagelearncontactusGridDataProvider</argument>
            <!-- name of the data source same as in argument/js_config/provider -->
            <argument name="name" xsi:type="string">mladmincontactus_contactus_listing_data_source</argument>
             <argument name="primaryFieldName" xsi:type="string">id</argument>
            <argument name="requestFieldName" xsi:type="string">id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
                    <item name="update_url" xsi:type="url" path="mui/index/render"/>
                    <item name="storageConfig" xsi:type="array">
                        <item name="indexField" xsi:type="string">id</item>
                    </item>
                </item>
            </argument>
        </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">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="storageConfig" xsi:type="array">
                        <item name="namespace" xsi:type="string">mladmincontactus_contactus_listing</item>
                    </item>
                </item>
            </argument>
        </bookmark>
        <component name="columns_controls">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="columnsData" xsi:type="array">
                        <item name="provider" xsi:type="string">mladmincontactus_contactus_listing.mladmincontactus_contactus_listing.mladmincontactus_contactus_columns</item>
                    </item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/controls/columns</item>
                    <item name="displayArea" xsi:type="string">dataGridActions</item>
                </item>
            </argument>
        </component>
        <filterSearch name="fulltext">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="provider" xsi:type="string">mladmincontactus_contactus_listing.mladmincontactus_contactus_listing_data_source</item>
                    <item name="chipsProvider" xsi:type="string">mladmincontactus_contactus_listing.mladmincontactus_contactus_listing.listing_top.listing_filters_chips</item>
                    <item name="storageConfig" xsi:type="array">
                        <item name="provider" xsi:type="string">mladmincontactus_contactus_listing.mladmincontactus_contactus_listing.listing_top.bookmarks</item>
                        <item name="namespace" xsi:type="string">current.search</item>
                    </item>
                </item>
            </argument>
        </filterSearch>
        <filters name="listing_filters">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="columnsProvider" xsi:type="string">mladmincontactus_contactus_listing.mladmincontactus_contactus_listing.mladmincontactus_contactus_columns</item>
                    <item name="storageConfig" xsi:type="array">
                        <item name="provider" xsi:type="string">mladmincontactus_contactus_listing.mladmincontactus_contactus_listing.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">mladmincontactus_contactus_listing.mladmincontactus_contactus_listing.listing_top.listing_filters</item>
                        <item name="imports" xsi:type="array">
                            <item name="visible" xsi:type="string">mladmincontactus_contactus_listing.mladmincontactus_contactus_listing.mladmincontactus_contactus_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">mladmincontactus_contactus_listing.mladmincontactus_contactus_listing.mladmincontactus_contactus_columns.ids</item>
                    <item name="indexField" xsi:type="string">id</item>
                </item>
            </argument>
            <action name="delete">
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="type" xsi:type="string">delete</item>
                        <item name="label" xsi:type="string" translate="true">Delete</item>
                        <item name="url" xsi:type="url" path="mladmincontactus/contactus/massDelete"/>
                        <item name="confirm" xsi:type="array">
                            <item name="name" xsi:type="string" translate="true">Delete items</item>
                            <item name="message" xsi:type="string" translate="true">Are you sure you wan't to delete selected items?</item>
                        </item>
                    </item>
                </argument>
            </action>           
            <action name="edit">
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="type" xsi:type="string">edit</item>
                        <item name="label" xsi:type="string" translate="true">Edit</item>
                        <item name="callback" xsi:type="array">
                            <item name="provider" xsi:type="string">mladmincontactus_contactus_listing.mladmincontactus_contactus_listing.mladmincontactus_contactus_columns_editor</item>
                            <item name="target" xsi:type="string">editSelected</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">mladmincontactus_contactus_listing.mladmincontactus_contactus_listing.listing_top.bookmarks</item>
                        <item name="namespace" xsi:type="string">current.paging</item>
                    </item>
                    <item name="selectProvider" xsi:type="string">mladmincontactus_contactus_listing.mladmincontactus_contactus_listing.mladmincontactus_contactus_columns.ids</item>
                </item>
            </argument>
        </paging>
    </container>
    
    <!-- define the columns of my grid -->
    <columns name="mladmincontactus_contactus_columns">

        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <!-- Bookmarks behaviour -->
                <item name="storageConfig" xsi:type="array">
                    <item name="provider" xsi:type="string">mladmincontactus_contactus_listing.mladmincontactus_contactus_listing.listing_top.bookmarks</item>
                    <item name="namespace" xsi:type="string">current</item>
                </item>
                <item name="editorConfig" xsi:type="array">
                    <item name="selectProvider" xsi:type="string">mladmincontactus_contactus_listing.mladmincontactus_contactus_listing.mladmincontactus_contactus_columns.ids</item>
                    <item name="enabled" xsi:type="boolean">true</item>
                    <item name="indexField" xsi:type="string">id</item>
                    <item name="clientConfig" xsi:type="array">
                        <item name="saveUrl" xsi:type="url" path="mladmincontactus/contactus/inlineEdit"/>
                        <item name="validateBeforeSave" xsi:type="boolean">false</item>
                    </item>
                </item>
                <item name="childDefaults" xsi:type="array">
                    <item name="controlVisibility" xsi:type="boolean">true</item>
                    <item name="fieldAction" xsi:type="array">
                        <item name="provider" xsi:type="string">mladmincontactus_contactus_listing.mladmincontactus_contactus_listing.mladmincontactus_contactus_columns_editor</item>
                        <item name="target" xsi:type="string">startEdit</item>
                        <item name="params" xsi:type="array">
                            <item name="0" xsi:type="string">${ $.$data.rowIndex }</item>
                            <item name="1" xsi:type="boolean">true</item>
                        </item>
                    </item>
                    <item name="storageConfig" xsi:type="array">
                        <item name="provider" xsi:type="string">mladmincontactus_contactus_listing.mladmincontactus_contactus_listing.listing_top.bookmarks</item>
                        <item name="root" xsi:type="string">columns.${ $.index }</item>
                        <item name="namespace" xsi:type="string">current.${ $.storageConfig.root}</item>
                    </item>
                </item>
            </item>
        </argument>

        <selectionsColumn name="ids">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <!-- define which field will be used as ID -->
                    <item name="indexField" xsi:type="string">id</item>
                </item>
            </argument>
        </selectionsColumn>

        <!-- Column ID -->
        <column name="id">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">textRange</item>
                    <item name="sorting" xsi:type="string">asc</item>
                    <item name="label" xsi:type="string" translate="true">ID</item>
                </item>
            </argument>
        </column>
        
        <column name="name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="visible" xsi:type="boolean">true</item>
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">contactus</item>
                    <item name="dataScope" xsi:type="string">name</item>
                    <item name="label" xsi:type="string" translate="true">Name</item>
                    <item name="filter" xsi:type="string">text</item>
                    <item name="editor" xsi:type="array">
                        <item name="editorType" xsi:type="string">text</item>
                        <item name="validation" xsi:type="array">
                            <item name="required-entry" xsi:type="boolean">true</item>
                        </item>
                    </item>
                </item>
            </argument>
        </column>
        
        <column name="email">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="visible" xsi:type="boolean">true</item>
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">contactus</item>
                    <item name="dataScope" xsi:type="string">email</item>
                    <item name="label" xsi:type="string" translate="true">Email</item>
                    <item name="filter" xsi:type="string">text</item>
                    <item name="editor" xsi:type="array">
                        <item name="editorType" xsi:type="string">text</item>
                        <item name="validation" xsi:type="array">
                            <item name="required-entry" xsi:type="boolean">true</item>
                        </item>
                    </item>
                </item>
            </argument>
        </column>
        
         <column name="telephone">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="visible" xsi:type="boolean">true</item>
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">contactus</item>
                    <item name="dataScope" xsi:type="string">telephone</item>
                    <item name="label" xsi:type="string" translate="true">Telephone</item>
                    <item name="filter" xsi:type="string">text</item>
                    <item name="editor" xsi:type="array">
                        <item name="editorType" xsi:type="string">text</item>
                        <item name="validation" xsi:type="array">
                            <item name="required-entry" xsi:type="boolean">true</item>
                        </item>
                    </item>
                </item>
            </argument>
        </column>
         
        <column name="comment">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="visible" xsi:type="boolean">true</item>
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">contactus</item>
                    <item name="dataScope" xsi:type="string">comment</item>
                    <item name="label" xsi:type="string" translate="true">Comment</item>
                    <item name="filter" xsi:type="string">text</item>
                    <item name="editor" xsi:type="array">
                        <item name="editorType" xsi:type="string">text</item>
                        <item name="validation" xsi:type="array">
                            <item name="required-entry" xsi:type="boolean">true</item>
                        </item>
                    </item>
                </item>
            </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 On</item>
                </item>
            </argument>
        </column>
        <actionsColumn name="actions" class="Magelearn\Contactus\Ui\Component\Listing\Column\Actions">
            <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">id</item>
                </item>
            </argument>
        </actionsColumn>
    </columns>
</listing>

This file looks complex but it is not as complex as it seems.It is just a listing tag that contains 6 child tags:

- argument:

Here we declare the data_sources to use (which makes the links between your grid and the database) with the tag js_config.

We also declare the spinner, that is the name of the tag "columns" that will be used in our grid. We then declare our buttons in the buttons tag with a name, a label, a class and a target url.

- dataSource:

Here we define the dataProvider (the object that will fetch our data from database). With a "class" tag to define the name of the object to be used. This object will be defined later in the di.xml (dependency node file).

We give a name to our dataSource via the "name" attribute and then we give it the field to use as the id for the grid in the database ("primaryFieldName") and for the request ("requestFieldName"). We then define in "config" the component to use (here "Magento_Ui/js/grid/provider") and the identifier in our "indexField" which here has the value "id".

- listingToolbar:

where bookmarks, columns_controls, fulltext, mass actions, listing_filters and listing_paging are defined.

- columns:

It was defined above in the "spinner" section of the "argument" section, here it is named mladmincontactus_contactus_columns.

This area will allow us to define our columns with the identifier to be used to find oneself, the type of fields and filters to use for the grid, the type of sorting that will be used and a label.

-- actions

Here we will give path path of our action class and define action for our grid record. like edit,delete.

Now, We will create our dataProvider collection file.

Create Collection.php file inside Model/ResourceModel/Contactus/Grid folder. 

<?php

namespace Magelearn\Contactus\Model\ResourceModel\Contactus\Grid;

use Magento\Framework\Api\Search\SearchResultInterface;
use Magento\Framework\Search\AggregationInterface;
use Magelearn\Contactus\Model\ResourceModel\Contactus\Collection as QuoteCollection;

/**
 * Class Collection
 * Collection for displaying grid of documents
 */
class Collection extends QuoteCollection implements SearchResultInterface
{
    /**
     * @var AggregationInterface
     */
    protected $aggregations;
    public function __construct(
        \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory,
        \Psr\Log\LoggerInterface $logger,
        \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy,
        \Magento\Framework\Event\ManagerInterface $eventManager,
        $mainTable,
        $eventPrefix,
        $eventObject,
        $resourceModel,
        $model = 'Magento\Framework\View\Element\UiComponent\DataProvider\Document',
        $connection = null,
        \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null
    ) {
       
        parent::__construct(
            $entityFactory,
            $logger,
            $fetchStrategy,
            $eventManager,
            $connection,
            $resource
        );
        $this->_eventPrefix = $eventPrefix;
        $this->_eventObject = $eventObject;
        $this->_init($model, $resourceModel);
        $this->setMainTable($mainTable);
    }
    /**
     * @return AggregationInterface
     */
    public function getAggregations()
    {
        return $this->aggregations;
    }
    /**
     * @param AggregationInterface $aggregations
     * @return $this
     */
    public function setAggregations($aggregations)
    {
        $this->aggregations = $aggregations;
    }
    /**
     * Retrieve all ids for collection
     * Backward compatibility with EAV collection
     *
     * @param int $limit
     * @param int $offset
     * @return array
     */
    public function getAllIds($limit = null, $offset = null)
    {
        return $this->getConnection()->fetchCol($this->_getAllIdsSelect($limit, $offset), $this->_bindParams);
    }
    /**
     * Get search criteria.
     *
     * @return \Magento\Framework\Api\SearchCriteriaInterface|null
     */
    public function getSearchCriteria()
    {
        return null;
    }
    /**
     * Set search criteria.
     *
     * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
     * @return $this
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function setSearchCriteria(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria = null)
    {
        return $this;
    }
    /**
     * Get total count.
     *
     * @return int
     */
    public function getTotalCount()
    {
        return $this->getSize();
    }
    /**
     * Set total count.
     *
     * @param int $totalCount
     * @return $this
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function setTotalCount($totalCount)
    {
        return $this;
    }
    /**
     * Set items list.
     *
     * @param \Magento\Framework\Api\ExtensibleDataInterface[] $items
     * @return $this
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function setItems(array $items = null)
    {
        return $this;
    }
}

We will also create our DataProvider.php file inside Ui/Component folder.

<?php
 
namespace Magelearn\Contactus\Ui\Component;

use Magento\Framework\Api\FilterBuilder;
use Magento\Framework\Api\Search\SearchCriteriaBuilder;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\View\Element\UiComponent\DataProvider\Reporting;

class DataProvider extends \Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider
{
    /**
     * @param string $name
     * @param string $primaryFieldName
     * @param string $requestFieldName
     * @param Reporting $reporting
     * @param SearchCriteriaBuilder $searchCriteriaBuilder
     * @param RequestInterface $request
     * @param FilterBuilder $filterBuilder
     * @param array $meta
     * @param array $data
     */
    public function __construct(
        $name,
        $primaryFieldName,
        $requestFieldName,
        Reporting $reporting,
        SearchCriteriaBuilder $searchCriteriaBuilder,
        RequestInterface $request,
        FilterBuilder $filterBuilder,
        array $meta = [],
        array $data = []
    ) {
        parent::__construct(
            $name,
            $primaryFieldName,
            $requestFieldName,
            $reporting,
            $searchCriteriaBuilder,
            $request,
            $filterBuilder,
            $meta,
            $data
        );
    }
}

Now, as per our actions class defined in ui_component xml file, we will create our action class.
Create Actions.php file inside Ui/Component/Listing/Column folder.

<?php
 
namespace Magelearn\Contactus\Ui\Component\Listing\Column;

use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Ui\Component\Listing\Columns\Column;
use Magelearn\Contactus\Block\Adminhtml\Contactus\Grid\Renderer\Action\UrlBuilder;
use Magento\Framework\UrlInterface;

class Actions extends Column
{
    /** Url path */
    const CONTACTUS_URL_PATH_EDIT = 'mladmincontactus/contactus/edit';
    const CONTACTUS_URL_PATH_DELETE = 'mladmincontactus/contactus/delete';

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

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

    /**
     * @param ContextInterface $context
     * @param UiComponentFactory $uiComponentFactory
     * @param UrlBuilder $actionUrlBuilder
     * @param UrlInterface $urlBuilder
     * @param array $components
     * @param array $data
     * @param string $editUrl
     */
    public function __construct(
        ContextInterface $context,
        UiComponentFactory $uiComponentFactory,
        UrlBuilder $actionUrlBuilder,
        UrlInterface $urlBuilder,
        array $components = [],
        array $data = [],
        $editUrl = self::CONTACTUS_URL_PATH_EDIT
    ) {
        $this->urlBuilder = $urlBuilder;
        $this->actionUrlBuilder = $actionUrlBuilder;
        $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['id'])) {
                    $item[$name]['edit'] = [
                        'href' => $this->urlBuilder->getUrl($this->editUrl, ['id' => $item['id']]),
                        'label' => __('Edit')
                    ];
                    $item[$name]['delete'] = [
                        'href' => $this->urlBuilder->getUrl(
                            self::CONTACTUS_URL_PATH_DELETE,
                            ['id' => $item['id']]
                        ),
                       'label' => __('Delete'),
                        'confirm' => [
                            'title' => __('Delete %1',$item['id']),
                            'message' => __('Are you sure you wan\'t to delete a %1 record ?',$item['id'])
                        ]
                    ];
                }
            }
        }
        return $dataSource;
    }
}

Here we provide edit and delete action for grid record.
In that we add UrlBuilder dependancy file.

Create UrlBuilder.php file inside Block/Adminhtml/Contactus/Grid/Renderer/Action folder.

<?php
 
namespace Magelearn\Contactus\Block\Adminhtml\Contactus\Grid\Renderer\Action;

use Magento\Store\Api\StoreResolverInterface;

/**
 * Class UrlBuilder
 * @package Magelearn\Contactus\Block\Adminhtml\Contactus\Grid\Renderer\Action
 */

class UrlBuilder
{
    /**
     * @var \Magento\Framework\UrlInterface
    */
    protected $frontendUrlBuilder;
    /**
     * @param \Magento\Framework\UrlInterface $frontendUrlBuilder
    */
    public function __construct(\Magento\Framework\UrlInterface $frontendUrlBuilder)
    {
        $this->frontendUrlBuilder = $frontendUrlBuilder;
    }
    /**
     * Get action url
     *
     * @param string $routePath
     * @param string $scope
     * @param string $store
     * @return string
     */
    public function getUrl($routePath, $scope, $store)
    {
        $this->frontendUrlBuilder->setScope($scope);
        $href = $this->frontendUrlBuilder->getUrl(
            $routePath,
            [
                '_current' => false,
                '_query' => [StoreResolverInterface::PARAM_NAME => $store]
            ]
        );
        return $href;
    }
}

Now, in mladmincontactus_contactus_listing.xml file we have added new button with */*/new action. Which will trigger for to add new data from admin.

To display that page we will first create controller and its layout file.
Create NewAction.php file inside Controller/Adminhtml/Contactus folder.
It will redirect to Edit action as well.

<?php

namespace Magelearn\Contactus\Controller\Adminhtml\Contactus;

/**
 * Class NewAction
 * @package Magelearn\Contactus\Controller\Adminhtml\Contactus
 */

class NewAction extends \Magento\Backend\App\Action
{
    /**
     * Authorization level of a basic admin session
     *
    */
    const ADMIN_RESOURCE = 'Magelearn_Contactus::save';
    /**
    * @var \Magento\Framework\View\Result\PageFactory
    */
    protected $resultForwardFactory;

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

    /**
     * Forward to edit
     *
     * @return \Magento\Backend\Model\View\Result\Forward
     */
    public function execute()
    {
        $resultForward = $this->resultForwardFactory->create();
        return $resultForward->forward('edit');
    }
}

Create layout file mladmincontactus_contactus_new.xml file inside view/adminhtml/layout folder.

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="mladmincontactus_contactus_edit"/>
    <body/>
</page>

This file will check for the mladmincontactus_contactus_edit.xml file.

Create file mladmincontactus_contactus_edit.xml inside view/adminhtml/layout folder.

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

Create mladmincontactus_contactus_form.xml file inside view/adminhtml/ui_component folder.

<?xml version="1.0" encoding="UTF-8"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">mladmincontactus_contactus_form.contactus_form_data_source</item>
            <item name="deps" xsi:type="string">mladmincontactus_contactus_form.contactus_form_data_source</item>
        </item>
        <item name="label" xsi:type="string" translate="true">Contactus Information</item>
        <item name="config" xsi:type="array">
            <item name="dataScope" xsi:type="string">data</item>
            <item name="namespace" xsi:type="string">mladmincontactus_contactus_form</item>
        </item>
        <item name="template" xsi:type="string">templates/form/collapsible</item>
        <item name="buttons" xsi:type="array">
            <item name="back" xsi:type="string">Magelearn\Contactus\Block\Adminhtml\Contactus\Edit\BackButton</item>
            <item name="delete" xsi:type="string">Magelearn\Contactus\Block\Adminhtml\Contactus\Edit\DeleteButton</item>
            <item name="reset" xsi:type="string">Magelearn\Contactus\Block\Adminhtml\Contactus\Edit\ResetButton</item>
            <item name="save" xsi:type="string">Magelearn\Contactus\Block\Adminhtml\Contactus\Edit\SaveButton</item>
            <item name="save_and_continue" xsi:type="string">Magelearn\Contactus\Block\Adminhtml\Contactus\Edit\SaveAndContinueButton</item>
        </item>
    </argument>
    <dataSource name="contactus_form_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Magelearn\Contactus\Model\Contactus\DataProvider</argument>
            <argument name="name" xsi:type="string">contactus_form_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">id</argument>
            <argument name="requestFieldName" xsi:type="string">id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="submit_url" xsi:type="url" path="mladmincontactus/contactus/save"/>
                </item>
            </argument>
        </argument>
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
            </item>
        </argument>
    </dataSource>
     
    <fieldset name="general">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="label" xsi:type="string" translate="true">Contactus Form</item>
                <item name="sortOrder" xsi:type="number">19</item>
            </item>
        </argument>        
        <field name="id">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="visible" xsi:type="boolean">false</item>
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">contactus</item>
                    <item name="dataScope" xsi:type="string">id</item>
                </item>
            </argument>
        </field>        
        <field name="name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Name</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">contactus</item>
                    <item name="sortOrder" xsi:type="number">20</item>
                    <item name="dataScope" xsi:type="string">name</item>
                    <item name="validation" xsi:type="array">
                    <item name="required-entry" xsi:type="boolean">true</item>
                    </item>
                </item>
            </argument>
        </field>
        <field name="email">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Email</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">contactus</item>
                    <item name="sortOrder" xsi:type="number">21</item>
                    <item name="dataScope" xsi:type="string">email</item>
                    <item name="validation" xsi:type="array">
                    <item name="required-entry" xsi:type="boolean">true</item>
                    </item>
                </item>
            </argument>
        </field>
        <field name="telephone">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Telephone</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">contactus</item>
                    <item name="sortOrder" xsi:type="number">22</item>
                    <item name="dataScope" xsi:type="string">telephone</item>
                    <item name="validation" xsi:type="array">
                    <item name="required-entry" xsi:type="boolean">true</item>
                    </item>
                </item>
            </argument>
        </field>
        <field name="comment">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Comment</item>
                    <item name="formElement" xsi:type="string">textarea</item>
                    <item name="source" xsi:type="string">contactus</item>
                    <item name="sortOrder" xsi:type="number">23</item>
                    <item name="dataScope" xsi:type="string">comment</item>
                    <item name="validation" xsi:type="array">
                        <item name="required-entry" xsi:type="boolean">true</item>
                    </item>
                </item>
            </argument>
        </field>
    </fieldset>        
</form>

In this xml file, we give our dataProvider class path and submit_url path.

We also add configuration for Back, Delete, Reset, Save and SaveAndContinueButton button. 
After that all the fields and its necessary configuration which needed to display in form.

Now, We will create our dataProvider file.
Create DataProvider.php file inside Model/Contactus folder. 

<?php

namespace Magelearn\Contactus\Model\Contactus;

use Magelearn\Contactus\Model\ResourceModel\Contactus\CollectionFactory;
use Magento\Framework\App\Request\DataPersistorInterface;

/**
 * Class DataProvider
 * @package Magelearn\Contactus\Model\Contactus
 */

class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider
{

    protected $collection;

    /**
     * @var DataPersistorInterface
     */
    protected $dataPersistor;
    public $_storeManager;
    /**
     * @var array
     */
    protected $loadedData;

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

    /**
     * Get data
     *
     * @return array
     */
    public function getData()
    {

        if (isset($this->loadedData)) {
            return $this->loadedData;
        }
        $items = $this->collection->getItems();
        
        foreach ($items as $page) {
            $this->loadedData[$page->getId()] = $page->getData();
        }

        $data = $this->dataPersistor->get('magelearn_contactus');
        if (!empty($data)) {
            $page = $this->collection->getNewEmptyItem();
            $page->setData($data);
            $this->loadedData[$page->getId()] = $page->getData();
            $this->dataPersistor->clear('magelearn_contactus');
        }

        return $this->loadedData;
    }
}

As, you can see from that Controller/Adminhtml/Contactus/NewAction.php file, it is redirect to edit action.

So, we will create Edit.php file inside Controller/Adminhtml/Contactus folder.

<?php

namespace Magelearn\Contactus\Controller\Adminhtml\Contactus;

use Magento\Backend\App\Action;

/**
 * Class Edit
 * @package Magelearn\Contactus\Controller\Adminhtml\Contactus
 */

class Edit extends \Magento\Backend\App\Action
{
    const ADMIN_RESOURCE = 'Magelearn_Contactus::save';
    /**
     * Core registry
     *
     * @var \Magento\Framework\Registry
     */
    protected $_coreRegistry;
    /**
     * @var \Magento\Framework\View\Result\PageFactory
     */
    protected $resultPageFactory;
    /**
     * @var \Magelearn\Contactus\Model\Contactus
     */
    protected $model;
    /**
     * @param Action\Context $context
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     * @param \Magelearn\Contactus\Model\Contactus $model
     * @param \Magento\Framework\Registry $registry
     */
    public function __construct(
        Action\Context $context,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory,
        \Magelearn\Contactus\Model\Contactus $model,
        \Magento\Framework\Registry $registry
    ) {
        $this->resultPageFactory = $resultPageFactory;
        $this->_coreRegistry = $registry;
        $this->model = $model;
        parent::__construct($context);
    }
    /**
     * Init actions
     *
     * @return \Magento\Backend\Model\View\Result\Page
     */
    protected function _initAction()
    {
        $resultPage = $this->resultPageFactory->create();
        $resultPage->setActiveMenu('Magelearn_Contactus::base')
            ->addBreadcrumb(__('Contactus'), __('Contactus'))
            ->addBreadcrumb(__('Manage Contactus'), __('Manage Contactus'));
        return $resultPage;
    }
    public function execute()
    {

        $id = $this->getRequest()->getParam('id');

        if ($id) {
            $this->model->load($id);
            if (!$this->model->getId()) {
                $this->messageManager
                ->addError(__('This record no longer exists.'));
                $resultRedirect = $this->resultRedirectFactory->create();
                return $resultRedirect->setPath('*/*/');
            }
        }
        $resultPage = $this->_initAction();

        $resultPage->addBreadcrumb(
            $id ? __('Edit Contactus') : __('New Contactus'),
            $id ? __('Edit Contactus') : __('New Contactus')
        );
        $resultPage->getConfig()->getTitle()->prepend(__('Contactus'));
        $resultPage->getConfig()->getTitle()
            ->prepend($this->model->getId() ? __('Edit Contactus') : __('New Contactus'));
        return $resultPage;
    }
}

Now, as per submit_url defined in /view/adminhtml/ui_component/mladmincontactus_contactus_form.xml file 
we will create Save.php file inside Controller/Adminhtml/Contactus folder.

<?php

namespace Magelearn\Contactus\Controller\Adminhtml\Contactus;

use Magento\Backend\App\Action;
use Magelearn\Contactus\Model\Contactus;
use Magento\Framework\App\Request\DataPersistorInterface;
use Magento\Framework\Exception\LocalizedException;

/**
 * Class Save
 * @package Magelearn\Contactus\Controller\Adminhtml\Contactus
 */

class Save extends \Magento\Backend\App\Action
{
    const ADMIN_RESOURCE = 'Magelearn_Contactus::save';
    /**
     * @var PostDataProcessor
     */
    protected $dataProcessor;
    /**
     * @var DataPersistorInterface
     */
    protected $dataPersistor;
    /**
     * @var Contactus
     */
    protected $model;

    /**
     * @param Action\Context $context
     * @param PostDataProcessor $dataProcessor
     * @param DataPersistorInterface $dataPersistor
     */
    public function __construct(
        Action\Context $context,
        PostDataProcessor $dataProcessor,
        Contactus $model,
        DataPersistorInterface $dataPersistor
    ) {
        $this->dataProcessor = $dataProcessor;
        $this->dataPersistor = $dataPersistor;
        $this->model = $model;
        parent::__construct($context);
    }
    /**
     * Save action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        $data = $this->getRequest()->getPostValue();
        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultRedirectFactory->create();
        if ($data) {
            $data = $this->dataProcessor->filter($data);
           
            if (empty($data['id'])) {
                $data['id'] = null;
            }
            $id = $this->getRequest()->getParam('id');
            if ($id) {
                $this->model->load($id);
            }
            $this->model->setData($data);
			
            $this->_eventManager->dispatch(
                'mladmincontactus_contactus_prepare_save',
                ['Contactus' => $this->model, 'request' => $this->getRequest()]
            );

            if (!$this->dataProcessor->validate($data)) {
                return $resultRedirect->setPath('*/*/edit', ['id' => $this->model->getId(), '_current' => true]);
            }
            try {
                $this->model->save();
                $this->messageManager->addSuccess(__('You saved the record.'));
                $this->dataPersistor->clear('mladmincontactus');
                if ($this->getRequest()->getParam('back')) {
                    return $resultRedirect->setPath(
                        '*/*/edit',
                        ['id' => $this->model->getId(),
                         '_current' => true]
                    );
                }
                return $resultRedirect->setPath('*/*/');
            } catch (LocalizedException $e) {
                $this->messageManager->addError($e->getMessage());
            } catch (\Exception $e) {
                $this->messageManager->addException($e, __('Something went wrong while saving the record.'));
            }

            $this->dataPersistor->set('mladmincontactus', $data);
            return $resultRedirect->setPath('*/*/edit', ['id' => $this->getRequest()->getParam('id')]);
        }
        return $resultRedirect->setPath('*/*/');
    }
}

In Above Save action file, we have added PostDataProcessor in __construct to validate data.
For that we will create PostDataProcessor.php file inside Controller/Adminhtml/Contactus folder.

<?php

namespace Magelearn\Contactus\Controller\Adminhtml\Contactus;

/**
 * Class PostDataProcessor
 * @package Magelearn\Contactus\Controller\Adminhtml\Contactus
 */

class PostDataProcessor
{
    /**
     * @var \Magento\Framework\Stdlib\DateTime\Filter\Date
     */
    protected $dateFilter;
    /**
     * @var \Magento\Framework\View\Model\Layout\Update\ValidatorFactory
     */
    protected $validatorFactory;
    /**
     * @var \Magento\Framework\Message\ManagerInterface
     */
    protected $messageManager;
    /**
     * @param \Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter
     * @param \Magento\Framework\Message\ManagerInterface $messageManager
     * @param \Magento\Framework\View\Model\Layout\Update\ValidatorFactory $validatorFactory
     */
    public function __construct(
        \Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter,
        \Magento\Framework\Message\ManagerInterface $messageManager,
        \Magento\Framework\View\Model\Layout\Update\ValidatorFactory $validatorFactory
    ) {
        $this->dateFilter = $dateFilter;
        $this->messageManager = $messageManager;
        $this->validatorFactory = $validatorFactory;
    }
    /**
     * Filtering posted data. Converting localized data if needed
     *
     * @param array $data
     * @return array
    */
    public function filter($data)
    {
        $filterRules = [];
        foreach (['custom_theme_from', 'custom_theme_to'] as $dateField) {
            if (!empty($data[$dateField])) {
                $filterRules[$dateField] = $this->dateFilter;
            }
        }
        return (new \Zend_Filter_Input($filterRules, [], $data))->getUnescaped();
    }

    /**
     * Validate post data
     *
     * @param array $data
     * @return bool     Return FALSE if someone item is invalid
     */
    public function validate($data)
    {
        $errorNo = true;
        if (!empty($data['layout_update_xml']) || !empty($data['custom_layout_update_xml'])) {
            /** @var $validatorCustomLayout \Magento\Framework\View\Model\Layout\Update\Validator */
            $validatorCustomLayout = $this->validatorFactory->create();
            if (!empty($data['layout_update_xml']) && !$validatorCustomLayout->isValid($data['layout_update_xml'])) {
                $errorNo = false;
            }
            if (!empty($data['custom_layout_update_xml'])
                && !$validatorCustomLayout->isValid($data['custom_layout_update_xml'])
            ) {
                $errorNo = false;
            }
            foreach ($validatorCustomLayout->getMessages() as $message) {
                $this->messageManager->addError($message);
            }
        }
        return $errorNo;
    }
    /**
     * Check if required fields is not empty
     *
     * @param array $data
     * @return bool
    */
    public function validateRequireEntry(array $data)
    {
        $requiredFields = [
            'title' => __('Page Title'),
            'stores' => __('Contactus View'),
            'is_active' => __('Status')
        ];
        $errorNo = true;
        foreach ($data as $field => $value) {
            if (in_array($field, array_keys($requiredFields)) && $value == '') {
                $errorNo = false;
                $this->messageManager->addError(
                    __('To apply changes you should fill in hidden required "%1" field', $requiredFields[$field])
                );
            }
        }
        return $errorNo;
    }
}

As per defined in mladmincontactus_contactus_form.xml file in buttons node, we will add all buttons file.

First we will add GenericButton.php file then all buttons will extends this file.
Create GenericButton.php file inside Block/Adminhtml/Contactus/Edit folder. 

<?php
 
namespace Magelearn\Contactus\Block\Adminhtml\Contactus\Edit;

use Magento\Backend\Block\Widget\Context;
use Magento\Cms\Api\PageRepositoryInterface;
use Magento\Framework\Exception\NoSuchEntityException;

/**
 * Class GenericButton
 * @package Magelearn\Contactus\Block\Adminhtml\Contactus\Edit
 */

class GenericButton
{
    /**
     * @var Context
    */
    protected $context;

    /**
     * @var PageRepositoryInterface
    */
    protected $pageRepository;
    /**
     * @param Context $context
     * @param PageRepositoryInterface $pageRepository
    */
    public function __construct(
        Context $context,
        PageRepositoryInterface $pageRepository
    ) {
        $this->context = $context;
        $this->pageRepository = $pageRepository;
    }
    public function getPageId()
    {
        try {
            return $this->pageRepository->getById(
                $this->context->getRequest()->getParam('id')
            )->getId();
        } catch (NoSuchEntityException $e) {
            return null;
        }
        return null;
    }
    /**
     * Generate url by route and parameters
     *
     * @param   array $params
     * @param   string $route
     * @return  string
    */
    public function getUrl($route = '', $params = [])
    {
        return $this->context->getUrlBuilder()->getUrl($route, $params);
    }
}

Create BackButton.php file inside Block/Adminhtml/Contactus/Edit folder

<?php

namespace Magelearn\Contactus\Block\Adminhtml\Contactus\Edit;

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

/**
 * Class BackButton
 * @package Magelearn\Contactus\Block\Adminhtml\Contactus\Edit
 */

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

Create DeleteButton.php file inside Block/Adminhtml/Contactus/Edit folder

<?php
 
namespace Magelearn\Contactus\Block\Adminhtml\Contactus\Edit;

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

/**
 * Class DeleteButton
 * @package Magelearn\Contactus\Block\Adminhtml\Contactus\Edit
 */

class DeleteButton extends GenericButton implements ButtonProviderInterface
{

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

    /**
     * @return string
     */
    public function getDeleteUrl()
    {
        return $this->getUrl('*/*/delete', ['id' => $this->getPageId()]);
    }
}

Create ResetButton.php file inside Block/Adminhtml/Contactus/Edit folder

<?php
 
namespace Magelearn\Contactus\Block\Adminhtml\Contactus\Edit;

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

/**
 * Class ResetButton
 * @package Magelearn\Contactus\Block\Adminhtml\Contactus\Edit
 */

class ResetButton implements ButtonProviderInterface
{
    /**
     * @return array
     */
    public function getButtonData()
    {
        return [
            'label' => __('Reset'),
            'class' => 'reset',
            'on_click' => 'location.reload();',
            'sort_order' => 30
        ];
    }
}

Create SaveAndContinueButton.php file inside Block/Adminhtml/Contactus/Edit folder

<?php
 
namespace Magelearn\Contactus\Block\Adminhtml\Contactus\Edit;

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

/**
 * Class SaveAndContinueButton
 * @package Magelearn\Contactus\Block\Adminhtml\Contactus\Edit
 */

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

Create SaveButton.php file inside Block/Adminhtml/Contactus/Edit folder

<?php
 
namespace Magelearn\Contactus\Block\Adminhtml\Contactus\Edit;

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

/**
 * Class SaveButton
 * @package Magelearn\Contactus\Block\Adminhtml\Contactus\Edit
 */
class SaveButton extends GenericButton implements ButtonProviderInterface
{
    /**
     * @return array
     */
    public function getButtonData()
    {
        return [
            'label' => __('Save'),
            'class' => 'save primary',
            'data_attribute' => [
                'mage-init' => ['button' => ['event' => 'save']],
                'form-role' => 'save',
            ],
            'sort_order' => 90,
        ];
    }
}

Now, almost everythig is done now.
Just to manage other actions (Like Delete, InlineEdit and MassDelete)  we will create associate controller file to handle those actions.

Create Delete.php file inside Contactus/Controller/Adminhtml/Contactus folder.

<?php
 
namespace Magelearn\Contactus\Controller\Adminhtml\Contactus;

use Magento\Backend\App\Action;

/**
 * Class Delete
 * @package Magelearn\Contactus\Controller\Adminhtml\Contactus
 */

class Delete extends \Magento\Backend\App\Action
{
    const ADMIN_RESOURCE = 'Magelearn_Contactus::contactus_delete';
    /**
     * @var \Magelearn\Contactus\Model\Contactus
     */
    protected $model;
    public function __construct(
        Action\Context $context,
        \Magelearn\Contactus\Model\Contactus $model
    ) {
        $this->model = $model;
        parent::__construct($context);
    }
    public function execute()
    {
        $id = $this->getRequest()->getParam('id');
        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultRedirectFactory->create();
        if ($id) {
            $title = "";
            try {
                $this->model->load($id);
                $title = $this->model->getTitle();
                $this->model->delete();
                // display success message
                $this->messageManager->addSuccess(__('The record has been deleted.'));
                // go to grid
                $this->_eventManager->dispatch(
                    'adminhtml_contactuspage_on_delete',
                    ['title' => $title, 'status' => 'success']
                );
                return $resultRedirect->setPath('*/*/');
            } catch (\Exception $e) {
                $this->_eventManager->dispatch(
                    'adminhtml_contactuspage_on_delete',
                    ['title' => $title, 'status' => 'fail']
                );
                // display error message
                $this->messageManager->addError($e->getMessage());
                // go back to edit form
                return $resultRedirect->setPath('*/*/edit', ['id' => $id]);
            }
        }
        // display error message
        $this->messageManager->addError(__('We can\'t find a record to delete.'));
        // go to grid
        return $resultRedirect->setPath('*/*/');
    }
}

Create InlineEdit.php file inside Contactus/Controller/Adminhtml/Contactus folder.

<?php

namespace Magelearn\Contactus\Controller\Adminhtml\Contactus;

use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Cms\Api\Data\PageInterface;
use Magelearn\Contactus\Model\Contactus as ModelContactus;

/**
 * Class InlineEdit
 * @package Magelearn\Contactus\Controller\Adminhtml\Contactus
 */

class InlineEdit extends \Magento\Backend\App\Action
{
    protected $dataProcessor;
    protected $jsonFactory;
    protected $ContactusModel;

    public function __construct(
        Context $context,
        PostDataProcessor $dataProcessor,
        ModelContactus $ContactusModel,
        JsonFactory $jsonFactory
    ) {
        parent::__construct($context);
        $this->dataProcessor = $dataProcessor;
        $this->jsonFactory = $jsonFactory;
        $this->ContactusModel = $ContactusModel;
    }

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

        foreach (array_keys($postItems) as $Id) {
            $Contactus = $this->ContactusModel->load($Id);
           
            try {
                $Data = $this->filterPost($postItems[$Id]);
                $this->validatePost($Data, $Contactus, $error, $messages);
                $extendedPageData = $Contactus->getData();
                $this->setContactusTableData($Contactus, $extendedPageData, $Data);
                $this->ContactusModel->save($Contactus);
            } catch (\Magento\Framework\Exception\LocalizedException $e) {
                $messages[] = $this->getErrorWithPageId($Contactus, $e->getMessage());
                $error = true;
            } catch (\RuntimeException $e) {
                $messages[] = $this->getErrorWithPageId($Contactus, $e->getMessage());
                $error = true;
            } catch (\Exception $e) {
                $messages[] = $this->getErrorWithPageId(
                    $Contactus,
                    __('Something went wrong while saving the item.')
                );
                $error = true;
            }
        }

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

    protected function filterPost($postData = [])
    {
        $pageData = $this->dataProcessor->filter($postData);
        $pageData['custom_theme'] = isset($pageData['custom_theme']) ? $pageData['custom_theme'] : null;
        $pageData['custom_root_template'] = isset($pageData['custom_root_template'])
            ? $pageData['custom_root_template']
            : null;
        return $pageData;
    }
    
    protected function validatePost(
        array $pageData,
        \Magelearn\Contactus\Model\Contactus $page,
        &$error,
        array &$messages
    ) {
        if (!($this->dataProcessor->validate($pageData) && $this->dataProcessor->validateRequireEntry($pageData))) {
            $error = true;
            foreach ($this->messageManager->getMessages(true)->getItems() as $error) {
                $messages[] = $this->getErrorWithPageId($page, $error->getText());
            }
        }
    }
     
    protected function getErrorWithPageId(ModelContactus $page, $errorText)
    {
        return '[Page ID: ' . $page->getId() . '] ' . $errorText;
    }
      
    public function setContactusTableData(
        ModelContactus $page,
        array $extendedPageData,
        array $pageData
    ) {
        $page->setData(array_merge($page->getData(), $extendedPageData, $pageData));
        return $this;
    }
}

Create MassDelete.php file inside Contactus/Controller/Adminhtml/Contactus folder.

<?php

namespace Magelearn\Contactus\Controller\Adminhtml\Contactus;

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

/**
 * Class MassDelete
 * @package Magelearn\Contactus\Controller\Adminhtml\Contactus
 */

class MassDelete extends \Magento\Backend\App\Action
{
    /**
     * @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);
    }
    /**
     * 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 $page) {
            $page->delete();
        }
        $this->messageManager->addSuccess(__('A total of %1 record(s) have been deleted.', $collectionSize));
        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
        return $resultRedirect->setPath('*/*/');
    }
}

After adding all those files, Run all Magento Commands:

php bin/magento set:upg

php bin/magento set:d:c

php bin/magento set:s:d -f en_US

php bin/magento c:c

php bin/magento c:f

You can see Admin Grid is ready and created !! :)




0 Comments On "Magento2 Save Contactus Data Into Databse and display in admin grid"

Back To Top