Admin Grid CRUD In Magento 2

In the previous article, I introduced How To Add Mass Actions In Magento 2. In this article, we will learn about Admin Grid CRUD In Magento 2.

This is a series on Magento 2 Extension Tutorial. The lessons are related to each other so you should read step by step.

Module File Structure

We updated our module file structure looks as follows:

admin grid crud folder structure

Create Edit Form

First, You create Edit.php in the Block folder following the path: Magetop/Helloworld/Block/Adminhtml/Posts/Edit.php

<?php
namespace Magetop\Helloworld\Block\Adminhtml\Posts;

use Magento\Backend\Block\Widget\Form\Container;
use Magento\Backend\Block\Widget\Context;
use Magento\Framework\Registry;

class Edit extends Container
{
    /**
     * Core registry
     *
     * @var \Magento\Framework\Registry
     */
    protected $_coreRegistry = null;

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

    /**
     * Class constructor
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_objectId = 'id';
        $this->_controller = 'adminhtml_posts';
        $this->_blockGroup = 'Magetop_Helloworld';

        parent::_construct();

        $this->buttonList->update('save', 'label', __('Save'));
        $this->buttonList->add(
            'saveandcontinue',
            [
                'label' => __('Save and Continue Edit'),
                'class' => 'save',
                'data_attribute' => [
                    'mage-init' => [
                        'button' => [
                            'event' => 'saveAndContinueEdit',
                            'target' => '#edit_form'
                        ]
                    ]
                ]
            ],
            -100
        );
        $this->buttonList->update('delete', 'label', __('Delete'));
    }

    /**
     * Retrieve text for header element depending on loaded news
     *
     * @return string
     */
    public function getHeaderText()
    {
        $posts = $this->_coreRegistry->registry('magetop_blog');
        if ($posts->getId()) {
            $postsTitle = $this->escapeHtml($posts->getTitle());
            return __("Edit News '%1'", $postsTitle);
        } else {
            return __('Add News');
        }
    }

    /**
     * Prepare layout
     *
     * @return \Magento\Framework\View\Element\AbstractBlock
     */
    protected function _prepareLayout()
    {
        $this->_formScripts[] = "
            function toggleEditor() {
                if (tinyMCE.getInstanceById('post_content') == null) {
                    tinyMCE.execCommand('mceAddControl', false, 'post_content');
                } else {
                    tinyMCE.execCommand('mceRemoveControl', false, 'post_content');
                }
            };
        ";

        return parent::_prepareLayout();
    }
}

Next, create file Magetop/Helloworld/Block/Adminhtml/Posts/Edit/Tabs.php

<?php
namespace Magetop\Helloworld\Block\Adminhtml\Posts\Edit;

use Magento\Backend\Block\Widget\Tabs as WidgetTabs;

class Tabs extends WidgetTabs
{
    /**
     * Class constructor
     *
     * @return void
     */
    protected function _construct()
    {
        parent::_construct();
        $this->setId('post_edit_tabs');
        $this->setDestElementId('edit_form');
        $this->setTitle(__('Post Information'));
    }

    /**
     * @return $this
     */
    protected function _beforeToHtml()
    {
        $this->addTab(
            'post_info',
            [
                'label' => __('General'),
                'title' => __('General'),
                'content' => $this->getLayout()->createBlock(
                    'Magetop\Helloworld\Block\Adminhtml\Posts\Edit\Tab\Info'
                )->toHtml(),
                'active' => true
            ]
        );

        return parent::_beforeToHtml();
    }
}

You create file Magetop/Helloworld/Block/Adminhtml/Posts/Edit/Form.php

<?php
namespace Magetop\Helloworld\Block\Adminhtml\Posts\Edit;

use Magento\Backend\Block\Widget\Form\Generic;

class Form extends Generic
{
    /**
     * @return $this
     */
    protected function _prepareForm()
    {
        /** @var \Magento\Framework\Data\Form $form */
        $form = $this->_formFactory->create(
            [
                'data' => [
                    'id'    => 'edit_form',
                    'action' => $this->getData('action'),
                    'method' => 'post',
                    'enctype' => 'multipart/form-data'
                ]
            ]
        );
        $form->setUseContainer(true);
        $this->setForm($form);

        return parent::_prepareForm();
    }
}

Then, you create Info.php according to the path: Magetop/Helloworld/Block/Adminhtml/Posts/Edit/Tab/Info.php

<?php
namespace Magetop\Helloworld\Block\Adminhtml\Posts\Edit\Tab;

use Magento\Backend\Block\Widget\Form\Generic;
use Magento\Backend\Block\Widget\Tab\TabInterface;
use Magento\Backend\Block\Template\Context;
use Magento\Framework\Registry;
use Magento\Framework\Data\FormFactory;
use Magento\Cms\Model\Wysiwyg\Config;
use Magetop\Helloworld\Model\System\Config\Status;

class Info extends Generic implements TabInterface
{
    /**
     * @var \Magento\Cms\Model\Wysiwyg\Config
     */
    protected $_wysiwygConfig;

    /**
     * @var \Magetop\Helloworld\Model\System\Config\Status
     */
    protected $_status;
    /**
     * @param Context $context
     * @param Registry $registry
     * @param FormFactory $formFactory
     * @param Config $wysiwygConfig
     * @param Status $status
     * @param array $data
     */
    public function __construct(
        Context $context,
        Registry $registry,
        FormFactory $formFactory,
        Config $wysiwygConfig,
        Status $status,
        array $data = []
    ) {
        $this->_wysiwygConfig = $wysiwygConfig;
        $this->_status = $status;
        parent::__construct($context, $registry, $formFactory, $data);
    }

    /**
     * Prepare form fields
     *
     * @return \Magento\Backend\Block\Widget\Form
     */
    protected function _prepareForm()
    {
        /** @var $model \Magetop\Helloworld\Model\PostsFactory */
        $model = $this->_coreRegistry->registry('magetop_blog');

        /** @var \Magento\Framework\Data\Form $form */
        $form = $this->_formFactory->create();
        $form->setHtmlIdPrefix('post_');
        $form->setFieldNameSuffix('post');
        // new filed

        $fieldset = $form->addFieldset(
            'base_fieldset',
            ['legend' => __('General')]
        );

        if ($model->getId()) {
            $fieldset->addField(
                'id',
                'hidden',
                ['name' => 'id']
            );
        }
        $fieldset->addField(
            'title',
            'text',
            [
                'name'      => 'title',
                'label'     => __('Title'),
                'title' => __('Title'),
                'required' => true
            ]
        );
        $fieldset->addField(
            'image',
            'text',
            array(
                'name'      => 'image',
                'label'     => __('Image'),
                'title' => __('Image'),
            )
        );
        $fieldset->addField(
            'status',
            'select',
            [
                'name'      => 'status',
                'label'     => __('Status'),
                'options'   => $this->_status->toOptionArray()
            ]
        );
        $fieldset->addField('description', 'editor', [
            'name'      => 'description',
            'label'   => 'Description',
            'config'    => $this->_wysiwygConfig->getConfig(),
            'wysiwyg'   => true,
            'required'  => false
        ]);
        $data = $model->getData();
        $form->setValues($data);
        $this->setForm($form);

        return parent::_prepareForm();
    }
    /**
     * Prepare label for tab
     *
     * @return string
     */
    public function getTabLabel()
    {
        return __('Posts Info');
    }

    /**
     * Prepare title for tab
     *
     * @return string
     */
    public function getTabTitle()
    {
        return __('Posts Info');
    }

    /**
     * {@inheritdoc}
     */
    public function canShowTab()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function isHidden()
    {
        return false;
    }
}

Create Controller files

You create the files Edit, Delete, NewAction, Save in the Controller with the following path: Magetop/Helloworld/Controller/Adminhtml/Posts.

Edit.php

<?php
namespace Magetop\Helloworld\Controller\Adminhtml\Posts;

use Magetop\Helloworld\Controller\Adminhtml\Posts;

class Edit extends Posts
{
    /**
     * @return void
     */
    public function execute()
    {
        $postId = $this->getRequest()->getParam('id');

        $model = $this->_postsFactory->create();

        if ($postId) {
            $model->load($postId);
            if (!$model->getId()) {
                $this->messageManager->addError(__('This news no longer exists.'));
                $this->_redirect('*/*/');
                return;
            }
        }

        // Restore previously entered form data from session
        $data = $this->_session->getNewsData(true);
        if (!empty($data)) {
            $model->setData($data);
        }
        $this->_coreRegistry->register('magetop_blog', $model);

        /** @var \Magento\Backend\Model\View\Result\Page $resultPage */
        $resultPage = $this->_resultPageFactory->create();
        $resultPage->setActiveMenu('Magetop_Helloworld::helloworld_menu');
        $resultPage->getConfig()->getTitle()->prepend(__('Posts'));

        return $resultPage;
    }
}

NewAction.php

<?php
namespace Magetop\Helloworld\Controller\Adminhtml\Posts;

use Magetop\Helloworld\Controller\Adminhtml\Posts;

class NewAction extends Posts
{
    /**
     * Create new news action
     *
     * @return void
     */
    public function execute()
    {
        $this->_forward('edit');
    }
}

Delete.php

<?php
namespace Magetop\Helloworld\Controller\Adminhtml\Posts;

use Magetop\Helloworld\Controller\Adminhtml\Posts;

class Delete extends Posts
{
    public function execute()
    {
        $postId = (int) $this->getRequest()->getParam('id');

        if ($postId) {
            /** @var $postModel \Magetop\Hellworld\Model\Posts */
            $postModel = $this->_postsFactory->create();
            $postModel->load($postId);

            // Check this news exists or not
            if (!$postModel->getId()) {
                $this->messageManager->addError(__('This news no longer exists.'));
            } else {
                try {
                    // Delete news
                    $postModel->delete();
                    $this->messageManager->addSuccess(__('The news has been deleted.'));

                    // Redirect to grid page
                    $this->_redirect('*/*/');
                    return;
                } catch (\Exception $e) {
                    $this->messageManager->addError($e->getMessage());
                    $this->_redirect('*/*/edit', ['id' => $postModel->getId()]);
                }
            }
        }
    }
}

Save.php

<?php
namespace Magetop\Helloworld\Controller\Adminhtml\Posts;

use Magetop\Helloworld\Controller\Adminhtml\Posts;

class Save extends Posts
{
    /**
     * @return void
     */
    public function execute()
    {
        $isPost = $this->getRequest()->getPost();

        if ($isPost) {
            $postsModel = $this->_postsFactory->create();
            $postsId = $this->getRequest()->getParam('id');

            if ($postsId) {
                $postsModel->load($postsId);
            }
            $formData = $this->getRequest()->getParam('post');
            $postsModel->setData($formData);

            try {
                // Save news
                $postsModel->save();

                // Display success message
                $this->messageManager->addSuccess(__('The news has been saved.'));

                // Check if 'Save and Continue'
                if ($this->getRequest()->getParam('back')) {
                    $this->_redirect('*/*/edit', ['id' => $postsModel->getId(), '_current' => true]);
                    return;
                }

                // Go to grid page
                $this->_redirect('*/*/');
                return;
            } catch (\Exception $e) {
                $this->messageManager->addError($e->getMessage());
            }

            $this->_getSession()->setFormData($formData);
            $this->_redirect('*/*/edit', ['id' => $postsId]);
        }
    }
}

Create two layout

Create helloworld_posts_create.xml in Magetop/Helloworld/view/adminhtml/layout/helloworld_posts_create.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
 * helloworld_posts_create
 *
 * @copyright Copyright © 2020 Magetop. All rights reserved.
 * @author    [email protected]
 */
-->
<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="../../../../../Magento/Core/etc/layout_single.xsd">
    <update handle="helloworld_posts_edit"/>
</layout>

Create helloworld_posts_edit.xml in Magetop/Helloworld/view/adminhtml/layout/helloworld_posts_edit.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
 * helloworld_posts_edit
 *
 * @copyright Copyright © 2020 Magetop. All rights reserved.
 * @author    [email protected]
 */
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="left">
            <block class="Magetop\Helloworld\Block\Adminhtml\Posts\Edit\Tabs"
                   name="helloworld_posts_grid_block.edit.tabs"/>
        </referenceContainer>
        <referenceContainer name="content">
            <block class="Magetop\Helloworld\Block\Adminhtml\Posts\Edit"
                   name="helloworld_posts_grid_block.edit"/>
        </referenceContainer>
    </body>
</page>

You access in Magento Admin Panel and check it.

check grid in admin panel
add new items in admin panel

I hope through this series you can create your own complete module. Good luck!

In addition to Admin Grid CRUD In Magento 2, you can read the articles How To Create New Theme In Magento 2.

Follow us for the more helpful article!

We hope this is a useful series for you.

Thank you for reading!

3.7 16 votes
Article Rating

Aaron LX

Aaron is a passionate writer, crazy about shopping, eCommerce and trends. Besides his outstanding research skills and a positive mind, Aaron eagerly shares his experience with the readers.

Leave a Reply or put your Question here

7 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Trung
Trung
May 13, 2020 9:42 am

Save button and Save and Continue button are not working

Adam Roger
Admin
May 14, 2020 10:03 am
Reply to  Trung

Thanks for your contribution! We will check it against

Nazmul
Nazmul
February 9, 2021 6:05 am

Please provide whole source code

Adam Roger
Admin
February 17, 2021 3:07 am
Reply to  Nazmul

Thanks for reading our article, sorry we do not provide the source code for this example

Web Wise
Web Wise
March 15, 2021 6:31 pm

In your controller classes, you include: use Magetop\Helloworld\Controller\Adminhtml\Posts;
But there are no Posts.php controllers defined.

Adam Roger
Admin
March 16, 2021 3:55 am
Reply to  Web Wise

You can please check this line : You create the files Edit, Delete, NewAction, Save in the Controller with the following path: Magetop/Helloworld/Controller/Adminhtml/Posts.

Saly
Saly
August 3, 2023 3:40 am

The function function toggleEditor() not working in magento 2.4.5 any more.
Can’t upload image when click to button.

7
0
Would love your thoughts, please comment.x
()
x