1

I'm trying to add a custom configuration field in system.xml like this where one of the columns will be a select element instead of a simple input element and will load values dynamically from the database.

enter image description here

Afterwards I want to save the serialized value in the database.

By reading the various tutorials online I understand that I have to actually create a custom field and create the custom frontend_model and backend_model

So I am trying with the following relevant parts of code which create the interface I want

system.xml

            <field id="availability_map" translate="label" sortOrder="10" showInDefault="1" showInStore="1" showInWebsite="1">
                <label>Availability Map</label>
                <frontend_model>Namespace\Module\Block\Adminhtml\Form\Field\AvailabilityMap</frontend_model>
                <backend_model>Namespace\Module\Config\Backend\AvailabilityMap</backend_model>
            </field>

Then I utilize the Select element class to render my option array in the first column.

Field/AvailabilityMap.php

class AvailabilityMap extends AbstractFieldArray{

    protected function _prepareToRender()
    {
        $availabilityAttributeCode = $this->defaultHelper->getPartialConfig('stock/availability_attribute');
        $this->productAttributeOption->setAttributeCode($availabilityAttributeCode);
        $options = $this->productAttributeOption->toOptionArray();

        $attributeOptionRenderer = $this->getLayout()
            ->createBlock(Select::class)
        ;
        $attributeOptionRenderer->setOptions($options);
        $this->addColumn('availability_option', [
            'label' => __('Availability Option'),
            'renderer' => $attributeOptionRenderer,
        ]);
        $this->addColumn('custom_availability_option', ['label' => __('Custom Availability Option'), 'class' => 'required-entry']);
        $this->_addAfter = false;
        $this->_addButtonLabel = __('Add Correlation');
    }
}

But I'm unable to save the values correctly. I tried this in my backend_model

Backend/AvailabilityMap.php

use Magento\Framework\App\Config\Value as ConfigValue;
use Magento\Framework\Serialize\SerializerInterface;

class AvailabilityMap extends ConfigValue
{
    /**
     * Json Serializer
     *
     * @var \Magento\Framework\Serialize\SerializerInterface
     */
    protected $serializer;

    public function __construct(\Magento\Framework\Model\Context $context, \Magento\Framework\Registry $registry, \Magento\Framework\App\Config\ScopeConfigInterface $config, \Magento\Framework\App\Cache\TypeListInterface $cacheTypeList, SerializerInterface $serializer, ?\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, ?\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [])
    {
        $this->serializer = $serializer;
        parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data);
    }


    /**
     * Prepare data before save
     *
     * @return void
     */
    public function beforeSave()
    {
        /** @var array $value */
        $value = $this->getValue();
        unset($value['__empty']);
        $encodedValue = $this->serializer->serialize($value);

        $this->setValue($encodedValue);
    }

    /**
     * Process data after load
     *
     * @return void
     */
    protected function _afterLoad()
    {
        /** @var string $value */
        $value = $this->getValue();
        if(empty($value)){
            $this->setValue(null);
        }
        else{
            $decodedValue = $this->serializer->unserialize($value);
            $this->setValue($decodedValue);
        }
    }


}

And the final result only saves the text input. I'm probably missing something in the way to transform the submitted data to the array I need to save.

Any help is appreciated.

1

Change backend model.

<backend_model>Magento\Config\Model\Config\Backend\Serialized\ArraySerialized</backend_model>

OR

Extend backend model from

Magento\Config\Model\Config\Backend\Serialized\ArraySerialized

[Update]

I tried following way:

<field id="availability_map" translate="label" sortOrder="10" showInDefault="1" showInStore="1" showInWebsite="1">
    <label>Availability Map</label>
    <frontend_model>SR\MagentoCommunity\Block\Adminhtml\Form\Field\AvailabilityMap</frontend_model>
    <backend_model>Magento\Config\Model\Config\Backend\Serialized\ArraySerialized</backend_model>
</field>

app/code/SR/MagentoCommunity/Block/Adminhtml/Form/Field/AvailabilityMap.php

<?php
namespace SR\MagentoCommunity\Block\Adminhtml\Form\Field;

use \Magento\Config\Block\System\Config\Form\Field\FieldArray\AbstractFieldArray;
use \Magento\Framework\DataObject;

class AvailabilityMap extends AbstractFieldArray
{
    protected $optionField;

    protected function _prepareToRender()
    {
        $this->addColumn('availability_option', [
            'label' => __('Availability Option'),
            'renderer' => $this->getOptionField(),
        ]);
        $this->addColumn('custom_availability_option', ['label' => __('Custom Availability Option'), 'class' => 'required-entry']);
        $this->_addAfter = false;
        $this->_addButtonLabel = __('Add Correlation');
    }

    /**
     * @return \SR\MagentoCommunity\Block\Adminhtml\Form\Field\OptionField
     */
    protected function getOptionField()
    {
        if (!$this->optionField) {
            $this->optionField = $this->getLayout()->createBlock(
                OptionField::class,
                '',
                ['data' => ['is_render_to_js_template' => true]]
            );
        }

        return $this->optionField;
    }

    /**
     * Prepare existing row data object
     *
     * @param DataObject $row
     * @return void
     */
    protected function _prepareArrayRow(DataObject $row)
    {
        $availabilityOption = $row->getAvailabilityOption();
        $options = [];
        if ($availabilityOption) {
            $options['option_' . $this->getOptionField()->calcOptionHash($availabilityOption)]
                = 'selected="selected"';
        }
        $row->setData('option_extra_attrs', $options);
    }
}

app/code/SR/MagentoCommunity/Block/Adminhtml/Form/Field/OptionField.php

<?php
namespace SR\MagentoCommunity\Block\Adminhtml\Form\Field;

use Magento\Framework\View\Element\Html\Select;

class OptionField extends Select
{
    /**
     * {@inheritdoc}
     */
    protected function _toHtml()
    {
        if (!$this->getOptions()) {

            $options = [
                [
                    'value' => 1,
                    'label' => 'Option 1'
                ],
                [
                    'value' => 2,
                    'label' => 'Option 2'
                ],
                [
                    'value' => 3,
                    'label' => 'Option 3'
                ]
            ];
            $this->setOptions($options);
        }

        return parent::_toHtml();
    }

    /**
     * Sets name for input element
     *
     * @param string $value
     * @return $this
     */
    public function setInputName($value)
    {
        return $this->setName($value);
    }
}

Here is the output after edit: enter image description here

  • Unfortunately I've tried both ways you mentioned and I have the same result. Only the second column is saved in the database. That's why I'm experimenting with creating my own backend_model for this – gabtzi May 3 at 10:31
  • Check updated answer – Sohel Rana May 3 at 11:11
  • So basically I was missing two steps as I understand it. 1) Extend the Select element instead of using the original class directly because it's missing the setInputName function so that the value can actually be stored in the db and 2) to extend the _prepareArrayRow so that I can preselect the saved option when loading the table. Thank you very much, your example helped me experiment and work through my issue by stepping around the various functions. – gabtzi May 3 at 21:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.