Magento 2 Admin Form

Continuing on from adding a UI Grid Component in Magento 2, here’s how to add a Magento 2 admin form.

To start with, add a controller for the ‘add’ post action. This will simply forward to the ‘edit’ controller action, as we can use the same functionality for both actions.

// app/code/Siphor/News/Controller/Adminhtml/Post/Add.php
<?php
namespace Siphor\News\Controller\Adminhtml\Post;

use \Magento\Backend\App\Action\Context;
use \Magento\Backend\Model\View\Result\ForwardFactory;
use \Magento\Backend\App\Action;

class Add extends Action
{
    protected $resultForwardFactory;

    public function __construct(
        Context $context,
        ForwardFactory $resultForwardFactory
    ) {
        $this->resultForwardFactory = $resultForwardFactory;
        parent::__construct($context);
    }

    /**
     * Forward to edit
     */
    public function execute()
    {
        $resultForward = $this->resultForwardFactory->create();
        return $resultForward->forward('edit');
    }
}

The Edit.php controller will be responsible for loading the form.

// app/code/Siphor/News/Controller/Adminhtml/Post/Edit.php
<?php
namespace Siphor\News\Controller\Adminhtml\Post;

use \Magento\Backend\App\Action;
use \Magento\Backend\App\Action\Context;
use \Magento\Framework\View\Result\PageFactory;
use \Magento\Framework\Registry;
use \Magento\Framework\Message\Manager;
use \Siphor\News\Model\PostFactory;

class Edit extends Action
{

    protected $_coreRegistry = null;

    protected $resultPageFactory;

    protected $_messageManager;

    protected $_postFactory;

    public function __construct(
        Context $context,
        PageFactory $resultPageFactory,
        Registry $registry,
        Manager $messageManager,
        PostFactory $postFactory
    ) {
        $this->resultPageFactory = $resultPageFactory;
        $this->_coreRegistry = $registry;
        $this->_messageManager = $messageManager;
        $this->_postFactory = $postFactory;
        parent::__construct($context);
    }

    protected function _initAction()
    {
        $resultPage = $this->resultPageFactory->create();
        $resultPage->setActiveMenu('Siphor_News::news')
            ->addBreadcrumb(__('News'), __('News'))
            ->addBreadcrumb(__('Manage News Posts'), __('Manage News Posts'));
        return $resultPage;
    }

    public function execute()
    {
        $id = $this->getRequest()->getParam('post_id');
        $model = $this->_postFactory->create();

        if ($id) {
            $model->load($id);
            if (!$model->getPostId()) {
                $this->_messageManager->addErrorMessage(__('This post no longer exists.'));
                $resultRedirect = $this->resultRedirectFactory->create();

                return $resultRedirect->setPath('*/*/');
            }
        }

        $data = $this->_getSession()->getFormData(true);
        if (!empty($data)) {
            $model->setData($data);
        }

        $this->_coreRegistry->register('news_post', $model);

        $resultPage = $this->_initAction();
        $resultPage->addBreadcrumb(
            $id ? __('Edit News Post') : __('New Post'),
            $id ? __('Edit News Post') : __('New Post')
        );
        $resultPage->getConfig()->getTitle()->prepend(__('News Posts'));
        $resultPage->getConfig()->getTitle()
            ->prepend($model->getPostId() ? $model->getPostTitle() : __('New Post'));

        return $resultPage;
    }
}

The Delete.php controller will delete a post when clicking on a ‘Delete’ button on the edit form page.

// app/code/Siphor/News/Controller/Adminhtml/Post/Delete.php
<?php
namespace Siphor\News\Controller\Adminhtml\Post;

use \Magento\Backend\App\Action;
use \Magento\Backend\App\Action\Context;
use \Magento\Framework\Message\Manager;
use \Siphor\News\Model\PostFactory;

class Delete extends Action
{

    protected $_messageManager;

    protected $_postFactory;

    public function __construct(
        Context $context,
        Manager $messageManager,
        PostFactory $postFactory
    )
    {
        parent::__construct($context);
        $this->_messageManager = $messageManager;
        $this->_postFactory = $postFactory;
    }

    /**
     * Delete action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        $id = $this->getRequest()->getParam('post_id');
        $resultRedirect = $this->resultRedirectFactory->create();
        if ($id) {
            try {
                $model = $this->_postFactory->create();
                $model->load($id);
                $model->delete();
                $this->_messageManager->addSuccessMessage(__('The post has been deleted.'));
                return $resultRedirect->setPath('*/*/');
            } catch (\Exception $e) {
                $this->_messageManager->addErrorMessage($e->getMessage());
                return $resultRedirect->setPath('*/*/edit', ['post_id' => $id]);
            }
        }
        $this->_messageManager->addErrorMessage(__('We can\'t find the post to delete.'));
        return $resultRedirect->setPath('*/*/');
    }
}

And finally, the Save.php controller will save any posts we add or edit.

// app/code/Siphor/News/Controller/Adminhtml/Post/Save.php
<?php
namespace Siphor\News\Controller\Adminhtml\Post;

use \Magento\Backend\App\Action;
use \Magento\Backend\App\Action\Context;
use \Magento\Framework\Message\Manager;
use \Siphor\News\Model\PostFactory;

class Save extends Action
{

    protected $_messageManager;

    protected $_postFactory;

    public function __construct(
        Context $context,
        Manager $messageManager,
        PostFactory $postFactory
    )
    {
        parent::__construct($context);
        $this->_messageManager = $messageManager;
        $this->_postFactory = $postFactory;
    }

    /**
     * Save action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        $data = $this->getRequest()->getPostValue();

        $resultRedirect = $this->resultRedirectFactory->create();
        if ($data) {
            $model = $this->_postFactory->create();

            $id = $this->getRequest()->getParam('post_id');
            if ($id) {
                $model->load($id);
            }

            $model->setData($data);

            try {
                $model->save();
                $this->_messageManager->addSuccessMessage(__('You saved this Post.'));
                $this->_getSession()->setFormData(false);
                if ($this->getRequest()->getParam('back')) {
                    return $resultRedirect->setPath('*/*/edit', ['post_id' => $model->getId(), '_current' => true]);
                }
                return $resultRedirect->setPath('*/*/');
            } catch (\Magento\Framework\Exception\LocalizedException $e) {
                $this->_messageManager->addErrorMessage($e->getMessage());
            } catch (\RuntimeException $e) {
                $this->_messageManager->addErrorMessage($e->getMessage());
            } catch (\Exception $e) {
                $this->_messageManager->addException($e, __('Something went wrong while saving the post.'));
            }

            $this->_getSession()->setFormData($data);
            return $resultRedirect->setPath('*/*/edit', ['post_id' => $this->getRequest()->getParam('post_id')]);
        }
        return $resultRedirect->setPath('*/*/');
    }
}

Now that the controllers have been added, we need to focus on the block classes.

The edit form will have a layout handle of ‘news_post_edit’ (based on the action URL of ‘news/post/edit’), therefore create a news_post_edit.xml file that will instantiate the Edit.php block class.

// app/code/Siphor/News/view/adminhtml/layout/news_post_edit.xml
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="editor"/>
    <body>
        <referenceContainer name="content">
            <block class="Siphor\News\Block\Adminhtml\Post\Edit" name="news_post_edit" />
        </referenceContainer>
    </body>
</page>

The Edit.php class renders the form action buttons, header text and returns the ‘Save and Continue Edit’ URL.

// app/code/local/Siphor/News/Block/Adminhtml/Post/Edit.php
<?php
namespace Siphor\News\Block\Adminhtml\Post;

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

class Edit extends Container
{

    protected $_coreRegistry = null;

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

    /**
     * Initialize news post edit block
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_objectId = 'post_id';
        $this->_blockGroup = 'Siphor_News';
        $this->_controller = 'adminhtml_post';

        parent::_construct();

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

        $this->buttonList->remove('reset');

    }

    public function getHeaderText()
    {
        if ($this->_coreRegistry->registry('news_post')->getId()) {
            return __("Edit Post '%1'", $this->escapeHtml($this->_coreRegistry->registry('news_post')->getPostTitle()));
        } else {
            return __('New Post');
        }
    }

    protected function _getSaveAndContinueUrl()
    {
        return $this->getUrl('news/*/save', ['_current' => true, 'back' => 'edit']);
    }
}

The Form.php class renders the form elements.

// app/code/local/Siphor/News/Block/Adminhtml/Post/Edit/Form.php
<?php
namespace Siphor\News\Block\Adminhtml\Post\Edit;

use \Magento\Backend\Block\Widget\Form\Generic;
use \Magento\Backend\Block\Template\Context;
use \Magento\Framework\Registry;
use \Magento\Framework\Data\FormFactory;
use \Magento\Store\Model\System\Store;
use \Magento\Cms\Model\Wysiwyg\Config;

class Form extends Generic
{

    protected $_systemStore;

    protected $_wysiwygConfig;

    /**
     * Form constructor.
     * @param Context $context
     * @param Registry $registry
     * @param FormFactory $formFactory
     * @param Store $systemStore
     * @param Config $wysiwygConfig
     * @param array $data
     */
    public function __construct(
        Context $context,
        Registry $registry,
        FormFactory $formFactory,
        Store $systemStore,
        Config $wysiwygConfig,
        array $data = []
    ) {
        $this->_systemStore = $systemStore;
        $this->_wysiwygConfig = $wysiwygConfig;
        parent::__construct($context, $registry, $formFactory, $data);
    }

    /**
     * Init form
     *
     * @return void
     */
    protected function _construct()
    {
        parent::_construct();
        $this->setId('post_form');
        $this->setTitle(__('Post Information'));
    }

    protected function _prepareForm()
    {
        $model = $this->_coreRegistry->registry('news_post');

        $form = $this->_formFactory->create(
            ['data' => ['id' => 'edit_form', 'action' => $this->getData('action'), 'method' => 'post']]
        );

        $form->setHtmlIdPrefix('post_');

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

        if ($model->getPostId()) {
            $fieldset->addField('post_id', 'hidden', ['name' => 'post_id']);
        }

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

        $fieldset->addField(
            'description',
            'editor',
            [
                'name' => 'description',
                'label' => __('Post Content'),
                'title' => __('Post Content'),
                'required' => true,
                'style' => 'height:10em',
                'config' => $this->_wysiwygConfig->getConfig()
            ]
        );

        $fieldset->addField(
            'status',
            'select',
            [
                'label' => __('Status'),
                'title' => __('Status'),
                'name' => 'status',
                'required' => true,
                'options' => ['1' => __('Enabled'), '0' => __('Disabled')]
            ]
        );

        if (!$model->getId()) {
            $model->setData('status', '1');
        }

        $form->setValues($model->getData());
        $form->setUseContainer(true);
        $this->setForm($form);

        return parent::_prepareForm();
    }
}

If you’ve followed all the steps correctly, you should have a fully functioning Magento 2 admin form.

Magento 2 Admin Form

Note: This article is based on Magento CE version 2.1.