Magento – Data population in UI form

adminformmagento-2.1magento2uicomponent

Im trying to build a custom admin form (using ui components) for a custom entity. We can assume the entity is very simple(e.g. id,name)

One thing that i have hard time with, is populating the form with data.

For this purpose i wrote a custom data provider which looks something like this:

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

    $items = $this->collection->getItems();
    /** @var Customer $customer */
    foreach ($items as $faq) {
        $this->loadedData[$faq->getId()] = $faq->getData();
    }

    $data = $this->getSession()->getFormData();
    if (!empty($data)) {
        $faqId = isset($data) ? $data : null;
        $this->loadedData[$faqId] = $data;
        $this->getSession()->unsFormData();
    }

    return $this->loadedData;
}

So the actual data are right under the id of the entity.

The ui component has a single fieldset with one field:

<fieldset name="general">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="label" xsi:type="string" translate="true">Conditions</item>
        </item>
    </argument>
    <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">Label</item>
                <item name="formElement" xsi:type="string">input</item>
                <item name="dataScope" xsi:type="string">name</item>
                <item name="source" xsi:type="string">general</item>
                <item name="validation" xsi:type="array">
                    <item name="required-entry" xsi:type="boolean">true</item>
                </item>
            </item>
        </argument>
    </field>
</fieldset>

The problem is, that the field never gets populated. Only way at the moment is to change stracture of the array returned by data provider:

$this->loadedData[$faq->getId()]['general'] = $faq->getData();

So the structure of the array has the same structure as fieldset -> field in the xml.

This seems to make some sense, however when i look how CMS module is defined for pages and blocks, it doesn't use the fieldset -> field hierarchy.

The data providers don't contain the fieldset key anywhere(or at least not from the getData) method, but the xml uses magical source 'block'.

For example for block

\Magento\Cms\Model\Block\DataProvider

public function getData()
{
    if (isset($this->loadedData)) {
        return $this->loadedData;
    }
    $items = $this->collection->getItems();
    /** @var \Magento\Cms\Model\Block $block */
    foreach ($items as $block) {
        $this->loadedData[$block->getId()] = $block->getData();
    }

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

    return $this->loadedData;
}

\Magento\Cms\view\ui_component\cms_block_form.xml

<fieldset name="general">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="label" xsi:type="string"/>
        </item>
    </argument>
    <field name="is_active">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="dataType" xsi:type="string">boolean</item>
                <item name="label" xsi:type="string" translate="true">Enable Block</item>
                <item name="formElement" xsi:type="string">checkbox</item>
                <item name="source" xsi:type="string">block</item>
                <item name="sortOrder" xsi:type="number">10</item>
                <item name="dataScope" xsi:type="string">is_active</item>
                <item name="prefer" xsi:type="string">toggle</item>
                <item name="valueMap" xsi:type="array">
                    <item name="true" xsi:type="number">1</item>
                    <item name="false" xsi:type="number">0</item>
                </item>
                <item name="default" xsi:type="number">1</item>
            </item>
        </argument>
    </field>

Where/How is this 'block' source created?

Do i really need to have the fieldset keys in data returned from the data provider?

Any help would be greatly appreciated.

Best Answer

Using dataPersistor to get Form data, thought you're using getSession

$data = $this->getSession()->getFormData();

Save action

<?php


namespace Vendor\Module\Controller\Adminhtml\Scheduler;

use Magento\Framework\Exception\LocalizedException;

class Save extends \Magento\Backend\App\Action
{

    protected $dataPersistor;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\App\Request\DataPersistorInterface $dataPersistor
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\App\Request\DataPersistorInterface $dataPersistor
    ) {
        $this->dataPersistor = $dataPersistor;
        parent::__construct($context);
    }

    /**
     * Save actionSave action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultRedirectFactory->create();
        $data = $this->getRequest()->getPostValue();
        if ($data) {
            $id = $this->getRequest()->getParam('scheduler_id');

            $model = $this->_objectManager->create('Vendor\Module\Model\Scheduler')->load($id);
            if (!$model->getId() && $id) {
                $this->messageManager->addErrorMessage(__('This Scheduler no longer exists.'));
                return $resultRedirect->setPath('*/*/');
            }

            $model->setData($data);

            try {

                $model->save();
                $this->messageManager->addSuccessMessage(__('You saved the Scheduler.'));
                $this->dataPersistor->clear('engraving_scheduler');

                if ($this->getRequest()->getParam('back')) {
                    return $resultRedirect->setPath('*/*/edit', ['scheduler_id' => $model->getId()]);
                }
                return $resultRedirect->setPath('*/*/');
            } catch (LocalizedException $e) {
                $this->messageManager->addErrorMessage($e->getMessage());
            } catch (\Exception $e) {
                $this->messageManager->addExceptionMessage($e, __('Something went wrong while saving the Scheduler.'));
            }

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

DataProvider

<?php


namespace Vendor\Module\Model\Scheduler;

use Vendor\Module\Model\ResourceModel\Scheduler\CollectionFactory;
use Magento\Framework\App\Request\DataPersistorInterface;

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

    protected $dataPersistor;

    protected $loadedData;
    protected $collection;


    /**
     * Constructor
     *
     * @param string $name
     * @param string $primaryFieldName
     * @param string $requestFieldName
     * @param CollectionFactory $collectionFactory
     * @param DataPersistorInterface $dataPersistor
     * @param array $meta
     * @param array $data
     */
    public function __construct(
        $name,
        $primaryFieldName,
        $requestFieldName,
        CollectionFactory $collectionFactory,
        DataPersistorInterface $dataPersistor,
        array $meta = [],
        array $data = []
    ) {
        $this->collection = $collectionFactory->create();
        $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 $model) {
            $this->loadedData[$model->getId()] = $model->getData();
        }
        $data = $this->dataPersistor->get('engraving_scheduler');

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

        return $this->loadedData;
    }
}

ui form xml

    <?xml version="1.0" ?>
<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">engraving_scheduler_form.scheduler_form_data_source</item>
            <item name="deps" xsi:type="string">engraving_scheduler_form.scheduler_form_data_source</item>
        </item>
        <item name="label" translate="true" xsi:type="string">General Information</item>
        <item name="config" xsi:type="array">
            <item name="dataScope" xsi:type="string">data</item>
            <item name="namespace" xsi:type="string">engraving_scheduler_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">Vendor\Module\Block\Adminhtml\Scheduler\Edit\BackButton</item>
            <item name="delete" xsi:type="string">Vendor\Module\Block\Adminhtml\Scheduler\Edit\DeleteButton</item>
            <item name="save" xsi:type="string">Vendor\Module\Block\Adminhtml\Scheduler\Edit\SaveButton</item>
            <item name="save_and_continue" xsi:type="string">Vendor\Module\Block\Adminhtml\Scheduler\Edit\SaveAndContinueButton</item>
        </item>
    </argument>
    <dataSource name="scheduler_form_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Vendor\Module\Model\Scheduler\DataProvider</argument>
            <argument name="name" xsi:type="string">scheduler_form_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">scheduler_id</argument>
            <argument name="requestFieldName" xsi:type="string">scheduler_id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="submit_url" path="*/*/save" xsi:type="url"/>
                </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"/>
            </item>
        </argument>
        <field name="label">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="label" translate="true" xsi:type="string">Title</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">Scheduler</item>
                    <item name="sortOrder" xsi:type="number">10</item>
                    <item name="dataScope" xsi:type="string">label</item>
                    <item name="validation" xsi:type="array">
                        <item name="required-entry" xsi:type="boolean">true</item>
                    </item>
                </item>
            </argument>
        </field>
    </fieldset>
</form>