Welcome to the third and last article of the Magento 2 Backend Configuration series. So far you’ve found out how to use source models (for providing data) and backend models (for managing actions before and after saving). Today we will focus on the Frontend Model responsible for our fields view. We don’t mean changing the height/width of the input, but a non-standard fields for our non-standard configuration.

We can define using frontend models in the Vendor/Module/etc/adminhtml/system.xml file in a similar way to the previous models, namely using the <frontend_model /> markers.

Magento vendor has a couple of frontend models defined by default, mainly for the PayPal module. However, the real power of the frontend model lies in connecting it to the backend model in Magento\Config\Model\Config\Backend\Serialized\ArraySerialized. The model serializes our data before saving, therefore, all options that we save will be found in only one field in the database. They simply need to be deserialized to be read.

Let’s begin! Imagine that the client wants to be able to define themselves the links in the menu navigation. We will need fields such as Name, Link and a dropdown with Active/Inactive options.

Let’s add to app/code/Magently/Tutorial/etc/adminhtml/system.xml a new group and a field with a custom frontend model together with (important!) a backend model responsible for serializing data.

//file app/code/Magently/Tutorial/etc/adminhtml/system.xml

...
            <group id="test_frontend" 
            translate="label" 
            sortOrder="1" 
            showInDefault="1" 
            showInWebsite="1" 
            showInStore="1">
                <label>Frontend model</label>
                    <field id="navigation_links" 
                        translate="label" 
                        sortOrder="4" 
                        showInDefault="1" 
                        showInWebsite="1" 
                        showInStore="1">
                    <label>Custom links in navigation</label>
                    <frontend_model>Magently\Tutorial\Block\Adminhtml\Form\Field\Navigation</frontend_model>
                    <backend_model>Magento\Config\Model\Config\Backend\Serialized\ArraySerialized</backend_model>
                </field>
            </group>
...

Now, let’s create our frontend model in the app/code/Magently/Tutorial/Block/Adminhtml/Form/Field directory.

//file app/code/Magently/Tutorial/Block/Adminhtml/Form/Field/Navigation.php

<?php

namespace Magently\Tutorial\Block\Adminhtml\Form\Field;

class Navigation extends \Magento\Config\Block\System\Config\Form\Field\FieldArray\AbstractFieldArray
{
    /**
     * @var $_attributesRenderer \Magently\Tutorial\Block\Adminhtml\Form\Field\Activation
     */
    protected $_activation;

    /**
     * Get activation options.
     *
     * @return \Magently\Tutorial\Block\Adminhtml\Form\Field\Activation
     */
    protected function _getActivationRenderer()
    {
        if (!$this->_activation) {
            $this->_activation = $this->getLayout()->createBlock(
                '\Magently\Tutorial\Block\Adminhtml\Form\Field\Activation',
                '',
                ['data' => ['is_render_to_js_template' => true]]
            );
        }

        return $this->_activation;
   }

    /**
     * Prepare to render.
     *
     * @return void
     */
    protected function _prepareToRender()
    {
        $this->addColumn('name', ['label' => __('Name')]);
        $this->addColumn('link', ['label' => __('Link')]);
        $this->addColumn(
            'activation_attribute',
            [
                'label' => __('Active'),
                'renderer' => $this->_getActivationRenderer()
            ]
        );

        $this->_addAfter = false;
        $this->_addButtonLabel = __('Add');
    }

    /**
     * Prepare existing row data object.
     *
     * @param \Magento\Framework\DataObject $row
     * @return void
     */
    protected function _prepareArrayRow(\Magento\Framework\DataObject $row)
    {
        $options = [];
        $customAttribute = $row->getData('activation_attribute');

        $key = 'option_' . $this->_getActivationRenderer()->calcOptionHash($customAttribute);
        $options[$key] = 'selected="selected"';
        $row->setData('option_extra_attrs', $options);
    }
}

We have three methods here, but for now let’s just focus on _prepareToRender(). We use a simple addColumn() method to add a new column to our table.

We also have a column with ‘activation_attribute’ ID where we use a rendered block to display a dropdown.

We have to create yet another block to use the other two methods. It will render the data for the ‘activation_attribute’ field.
Let’s create another file in the same directory:

//file app/code/Magently/Tutorial/Block/Adminhtml/Form/Field/Activation.php

<?php

namespace Magently\Tutorial\Block\Adminhtml\Form\Field;

class Activation extends \Magento\Framework\View\Element\Html\Select
{
    /**
     * Model Enabledisable
     *
     * @var \Magento\Config\Model\Config\Source\Enabledisable
     */
    protected $_enableDisable;

    /**
     * Activation constructor.
     *
     * @param \Magento\Framework\View\Element\Context $context
     * @param \Magento\Config\Model\Config\Source\Enabledisable $enableDisable $enableDisable
     * @param array $data
     */
    public function __construct(
        \Magento\Framework\View\Element\Context $context,
        \Magento\Config\Model\Config\Source\Enabledisable $enableDisable,
        array $data = []
    ) {
        parent::__construct($context, $data);

        $this->_enableDisable = $enableDisable;
    }

    /**
     * @param string $value
     * @return Magently\Tutorial\Block\Adminhtml\Form\Field\Activation
     */
    public function setInputName($value)
    {
        return $this->setName($value);
    }

    /**
     * Parse to html.
     *
     * @return mixed
     */
    public function _toHtml()
    {
        if (!$this->getOptions()) {
            $attributes = $this->_enableDisable->toOptionArray();

            foreach ($attributes as $attribute) {
                $this->addOption($attribute['value'], $attribute['label']);
            }
        }

        return parent::_toHtml();
    }
}

We need to provide the source model in the constructor (we talked about it in the first article of the series). Since we need a simple zero-one option, we will use an existing source model called Enabledisable. Of course, you can use your own source model if you like. Note that the creation process of our dropdown takes place in the _toHtml() method.Thanks to the toOptionArray() method from our source models, we can easily refer to the value and the label.

Now that we have the class, we can call the _getActivationRenderer() method in our frontend model that creates the block with the necessary data.

The final result is the following:

frontendmodel

And that’s it! This was the last article from Magento 2 backend model series. We hope you enjoyed them and that backend models are clearer to you now.