The form (uiComponent) of the backend and the CRUD in magento2 admin

This tutorial is the 8th in a long series of magento2 tutorial. It will be updated as the platform evolves. In the last episode, you learned how to create a grid in the admin of your magento2 module and use mass actions (MassAction). If you do not know magento (v1) it does not matter, I will talk about it but you do not need to know the magento platform (v1) to master this tutorial on magento2, make sure To have done the 8 previous tutorials. So as I said, in the previous tutorial we saw how to add a uiComponent listing (grid) to our module in the backend of magento2! We will now see how to add a form to Add / Modify / Delete (CRUD) our objects with in this module. For that, we will start from the files of our previous tutorial.

Display the form via the magento2 Action

To create our Form, we will first create the newAction action in our controller like this: (/Pfay/Contacts/Controller/Adminhtml/Contact/NewAction.php)

<?php
namespace Pfay\Contacts\Controller\Adminhtml\Test;
use Magento\Backend\App\Action;
use Pfay\Contacts\Model\Contact as Contact;

class NewAction extends \Magento\Backend\App\Action
{
    /**
     * Edit A Contact Page
     *
     * @return \Magento\Backend\Model\View\Result\Page|\Magento\Backend\Model\View\Result\Redirect
     * @SuppressWarnings(PHPMD.NPathComplexity)
     */
    public function execute()
    {
        $this->_view->loadLayout();
        $this->_view->renderLayout();

        $contactDatas = $this->getRequest()->getParam('contact');
        if(is_array($contactDatas)) {
            $contact = $this->_objectManager->create(Contact::class);
            $contact->setData($contactDatas)->save();
            $resultRedirect = $this->resultRedirectFactory->create();
            return $resultRedirect->setPath('*/*/index');
        }
    }
}
We load and then display the layout that will contain the form. We check if we receive the parameter "contact" (if the form is sent), if yes then we save the contact in the database
. Logically, we will now create the layout (/Pfay/Contacts/view/adminhtml/layout/contacts_test_newaction.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="contacts_test_edit"/>
    <body/>
</page>

In this layout, we just tell him to look for our layout contacts_text_edit. We create the layout file for editing (/Pfay/Contacts/view/adminhtml/layout/contacts_test_edit.xml):

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


Creating the file uiComponent for creation our form in magento2

Here it is said to load a uiComponent named "pfay_contacts_form", ( remember we did the same for the grid in the previous tutorial). We will therefore create the form in this uiComponent form.
So let's create our uiComponent file for our form ( app/code/Pfay/Contacts/view/adminhtml/ui_component/pfay_contacts_form.xml ) :

    <?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">pfay_contacts_form.contacts_form_data_source</item>
            <item name="deps" xsi:type="string">pfay_contacts_form.contacts_form_data_source</item>
        </item>
        <item name="label" xsi:type="string" translate="true">Sample Form</item>
        <item name="layout" xsi:type="array">
            <item name="type" xsi:type="string">tabs</item>
        </item>

        <item name="buttons" xsi:type="array">
            <item name="back" xsi:type="string">Pfay\Contacts\Block\Adminhtml\Contact\Edit\BackButton</item>
            <item name="delete" xsi:type="string">Pfay\Contacts\Block\Adminhtml\Contact\Edit\DeleteButton</item>
            <item name="reset" xsi:type="string">Pfay\Contacts\Block\Adminhtml\Contact\Edit\ResetButton</item>
            <item name="save" xsi:type="string">Pfay\Contacts\Block\Adminhtml\Contact\Edit\SaveButton</item>
        </item>
    </argument>

    <dataSource name="contacts_form_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Pfay\Contacts\Model\Contact\DataProvider</argument>
            <argument name="name" xsi:type="string">contacts_form_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">pfay_contacts_id</argument>
            <argument name="requestFieldName" xsi:type="string">id</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="contact">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="label" xsi:type="string" translate="true">Sample Fieldset</item>
            </item>
        </argument>

        <!-- This field represents form id and is hidden -->
        <field name="pfay_contact_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">contact</item>
                </item>
            </argument>
        </field>

        <!-- This field has data type 'text' and standard 'input' form element and looks like input -->
        <field name="name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="label" xsi:type="string">Name</item>
                    <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">contact</item>
                </item>
            </argument>
        </field>


        <!-- This field has data type 'text' and standard 'input' form element and looks like input -->
        <field name="email">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="label" xsi:type="string">Email</item>
                    <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">contact</item>
                </item>
            </argument>
        </field>

    </fieldset>
</form>

Here the file looks complicated but you have to break it down.Do not worry it's not that complicated. The file uiComponent for a form is composed of several tags:

  • argument: The base of the file, which allows to configure the data_source to use, the label, and the display of buttons
    • data: allows to make the links with the dataSource, pay attention to well define your "file name paths." of the datasource
    • buttons: if you have come up to it is that you know how to read an xml normally but we will still specify it is here that the buttons are added. For each button a name and a class to be used are delined. We'll come back to it later.
  • dataSource: The configuration of the dataSource itself, the links between the database and our form.
    The datasource defines the links between the database and your formualire, here it is given the name "contacts_form_data_source" and the class "Pfay\Contacts\Model\Contact\DataProvider". We define that the id in our table will be "pfay_contacts_id" but that it will be renamed "id" in our form.
  • fieldset: The fieldset is used to define the fields of your form.
    • field: represents a field of our form, they can be of several types that I invite you to search on the documentation of magento2. Here we used only text fields to facilitate our work. We define the same as the cahmps for the id will be invisible.

The DataSource of our magento2 form

The datasource of our magento2 form takes here our class "Pfay\Contacts\Model\Contact\DataProvider" so we will create it.
So create the app/code/Pfay/Contacts/Model/Contact/DataProvider.php file like this:

<?php
namespace Pfay\Contacts\Model\Contact;
use Pfay\Contacts\Model\ResourceModel\Contact\CollectionFactory;
class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider
{
    /**
     * @param string $name
     * @param string $primaryFieldName
     * @param string $requestFieldName
     * @param CollectionFactory $contactCollectionFactory
     * @param array $meta
     * @param array $data
     */
    public function __construct(
        $name,
        $primaryFieldName,
        $requestFieldName,
        CollectionFactory $contactCollectionFactory,
        array $meta = [],
        array $data = []
    ) {
        $this->collection = $contactCollectionFactory->create();
        parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
    }

    public function getData()
    {
        if (isset($this->loadedData)) {
            return $this->loadedData;
        }

        $items = $this->collection->getItems();
        $this->loadedData = array();
        /** @var Customer $customer */
        foreach ($items as $contact) {
            // notre fieldset s'apelle "contact" d'ou ce tableau pour que magento puisse retrouver ses datas :
            $this->loadedData[$contact->getId()]['contact'] = $contact->getData();
        }


        return $this->loadedData;

    }
}

It is this file that allows our fields to be automatically mapped to the database. The so-called "dataBinding". Here we called our fieldset "contact", so we pass our Contact object in the loadedData array that we return.

Configuring our form buttons

Under magento2, in the forms, we define classes for buttons like what we did in our uiComponent:

        <item name="buttons" xsi:type="array">
            <item name="back" xsi:type="string">Pfay\Contacts\Block\Adminhtml\Contact\Edit\BackButton</item>
            <item name="delete" xsi:type="string">Pfay\Contacts\Block\Adminhtml\Contact\Edit\DeleteButton</item>
            <item name="reset" xsi:type="string">Pfay\Contacts\Block\Adminhtml\Contact\Edit\ResetButton</item>
            <item name="save" xsi:type="string">Pfay\Contacts\Block\Adminhtml\Contact\Edit\SaveButton</item>
        </item>
We will create our files:

Create a GenericButton:

The GenericButton is the element that allows other buttons to exist. This is the base that extends the other buttons.
app/code/Pfay/Contacts/Block/Adminhtml/Contact/Edit/GenericButton.php

<?php
namespace Pfay\Contacts\Block\Adminhtml\Contact\Edit;

use Magento\Search\Controller\RegistryConstants;

/**
 * Class GenericButton
 */
class GenericButton
{
    /**
     * Url Builder
     *
     * @var \Magento\Framework\UrlInterface
     */
    protected $urlBuilder;

    /**
     * Registry
     *
     * @var \Magento\Framework\Registry
     */
    protected $registry;

    /**
     * Constructor
     *
     * @param \Magento\Backend\Block\Widget\Context $context
     * @param \Magento\Framework\Registry $registry
     */
    public function __construct(
        \Magento\Backend\Block\Widget\Context $context,
        \Magento\Framework\Registry $registry
    ) {
        $this->urlBuilder = $context->getUrlBuilder();
        $this->registry = $registry;
    }

    /**
     * Return the synonyms group Id.
     *
     * @return int|null
     */
    public function getId()
    {
        $contact = $this->registry->registry('contact');
        return $contact ? $contact->getId() : null;
    }

    /**
     * Generate url by route and parameters
     *
     * @param   string $route
     * @param   array $params
     * @return  string
     */
    public function getUrl($route = '', $params = [])
    {
        return $this->urlBuilder->getUrl($route, $params);
    }
}

Create a button to save our magento2 form

Create the file app/code/Pfay/Contacts/Block/Adminhtml/Contact/Edit/BackButton.php
In which you just configure the label of the button, the associated event and the form-role that automatically triggers events js for magento2.

<?php
namespace Pfay\Contacts\Block\Adminhtml\Contact\Edit;

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

/**
 * Class BackButton
 */
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 a button to delete an object using our form

Then create the file to delete an item from our formular
App/code/Pfay/Contacts/Block/Adminhtml/Contact/Edit/DeleteButton.php Here it is more or less pariel but for the suppression:

<?php
namespace Pfay\Contacts\Block\Adminhtml\Contact\Edit;

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

/**
 * Class DeleteButton
 */
class DeleteButton extends GenericButton implements ButtonProviderInterface
{
    /**
     * @return array
     */
    public function getButtonData()
    {
        $data = [];
        if ($this->getId()) {
            $data = [
                'label' => __('Delete Contact'),
                'class' => 'delete',
                'on_click' => 'deleteConfirm(\''
                    . __('Are you sure you want to delete this contact ?')
                    . '\', \'' . $this->getDeleteUrl() . '\')',
                'sort_order' => 20,
            ];
        }
        return $data;
    }

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

Create a reset button to reset our magento2 form

app/code/Pfay/Contacts/Block/Adminhtml/Contact/Edit/ResetButton.php

<?php
namespace Pfay\Contacts\Block\Adminhtml\Contact\Edit;

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

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

Create a reset button to save our form

app/code/Pfay/Contacts/Block/Adminhtml/Contact/Edit/SaveButton.php

<?php
namespace Pfay\Contacts\Block\Adminhtml\Contact\Edit;

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

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

And here are !! Our buttons are created and theoretically everything should work :)

Create our delete action

We create our delete action to remove an object from the magento grid like this:

<?php
namespace Pfay\Contacts\Controller\Adminhtml\Test;
use Pfay\Contacts\Model\Contact as Contact;
use Magento\Backend\App\Action;

class Delete extends \Magento\Backend\App\Action
{
    public function execute()
    {
        $id = $this->getRequest()->getParam('id');

        if (!($contact = $this->_objectManager->create(Contact::class)->load($id))) {
            $this->messageManager->addError(__('Unable to proceed. Please, try again.'));
            $resultRedirect = $this->resultRedirectFactory->create();
            return $resultRedirect->setPath('*/*/index', array('_current' => true));
        }
        try{
            $contact->delete();
            $this->messageManager->addSuccess(__('Your contact has been deleted !'));
        } catch (Exception $e) {
            $this->messageManager->addError(__('Error while trying to delete contact: '));
            $resultRedirect = $this->resultRedirectFactory->create();
            return $resultRedirect->setPath('*/*/index', array('_current' => true));
        }

        $resultRedirect = $this->resultRedirectFactory->create();
        return $resultRedirect->setPath('*/*/index', array('_current' => true));
    }
}
Congratulations to you who followed this tutorial to the end. These last 2 tutorials are not the easiest articles of training. It's quite long and complicated but it's worth it to spend a little time to understand this because admin forms and grids you will use them all the time under magento2.
If this tutorial tells you more or you have a question, share this article on twitter please! Thank you to those who will. You can find the sources of this tutorial just below
Available documents for this article :
Books that can help you :
  • Livre Magento 2 Developer's Guide by Branko
  • Livre Mastering Magento2 Second Edition
  • Livre Magento 2 Cookbook
Questions about this lesson
No questions for this lesson. Be the first !

You must be logged in to ask for help on a lesson.