type=”checkbox” Admin Field Saved as NULL in Magento 2?

adminadminformmagento2

I have added a basic admin form field in system.xml as follows:

<field id="do_something" translate="label comment" type="checkbox" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1">
    <label>Do something?</label>
    <comment>Do we want to do something?</comment>
</field>

The field renders correctly, and saves with no errors, and a row is added to core_config_data, but the value is always NULL regardless of whether the box was checked or not.

What am I missing?

Best Answer

As far as I know, by default Magento, we cannot save the value of checkbox with a simple declaration of checkbox type in xml config. So, we need to create our own custom renderer for this config.

Create admin config: app/code/Vendor/YourModule/etc/adminhtml/system.xml

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <tab id="sample_tab" translate="label" sortOrder="1000">
            <label>Example tab config</label>
        </tab>
        <section id="sample_section" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1">
            <label>Example config section</label>
            <tab>sample_tab</tab>
            <resource>Vendor_YourModule::config</resource>
            <group id="general" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>General</label>
                <field id="do_something" translate="label" type="checkbox" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Do something?</label>
                    <frontend_model>Vendor\YourModule\Block\Adminhtml\System\Config\Checkbox</frontend_model>
                    <comment>Do we want to do something?</comment>
                </field>
            </group>
        </section>
    </system>
</config>

Basically, our config declare a frontend model for rendering custom field.

Create frontend model block: app/code/Vendor/YourModule/Block/Adminhtml/System/Config/Checkbox.php

<?php

namespace Vendor\YourModule\Block\Adminhtml\System\Config;

use Magento\Framework\Data\Form\Element\AbstractElement;;

class Checkbox extends \Magento\Config\Block\System\Config\Form\Field
{
    const CONFIG_PATH = 'sample_section/general/do_something';

    protected $_template = 'Vendor_YourModule::system/config/checkbox.phtml';

    protected $_values = null;

    /**
     * Checkbox constructor.
     * @param \Magento\Backend\Block\Template\Context $context
     * @param array $data
     */
    public function __construct(
        \Magento\Backend\Block\Template\Context $context,
        array $data = []
    ) {
        parent::__construct($context, $data);
    }
    /**
     * Retrieve element HTML markup.
     *
     * @param AbstractElement $element
     *
     * @return string
     */
    protected function _getElementHtml(AbstractElement $element)
    {
        $this->setNamePrefix($element->getName())
            ->setHtmlId($element->getHtmlId());

        return $this->_toHtml();
    }

    public function getValues()
    {
        $values = [];
        $optionArray = \Vendor\YourModule\Model\Config\Source\Checkbox::toOptionArray();
        foreach ($optionArray as $value) {
            $values[$value['value']] = $value['label'];
        }
        return $values;
    }

    /**
     * Get checked value.
     * @param  $name
     * @return boolean
     */
    public function getIsChecked($name)
    {
        return in_array($name, $this->getCheckedValues());
    }
    /**
     *
     * Retrieve the checked values from config
     */
    public function getCheckedValues()
    {
        if (is_null($this->_values)) {
            $data = $this->getConfigData();
            if (isset($data[self::CONFIG_PATH])) {
                $data = $data[self::CONFIG_PATH];
            } else {
                $data = '';
            }
            $this->_values = explode(',', $data);
        }

        return $this->_values;
    }
}

Config source value: app/code/Vendor/YourModule/Model/Config/Source/Checkbox.php

<?php

namespace Vendor\YourModule\Model\Config\Source;

class Checkbox
{
    public static function toOptionArray()
    {
        return [['value' => 'checkbox', 'label'=>__('Do something?')]];
    }
}

app/code/Vendor/YourModule/view/adminhtml/templates/system/config/checkbox.phtml

<input type="hidden" name="<?php echo $this->getNamePrefix() ?>" value="" /><!-- this is send if nothing is checked -->
<ul class="checkboxes">
    <?php foreach ($this->getValues() as $name => $label): ?>
        <li>
            <input type="checkbox" value="<?php echo $name?>"
                   name="<?php echo $this->getNamePrefix() ?>[]"
                   id="<?php echo $this->getHtmlId() . '_' . $name ?>"
                <?php echo ($this->getIsChecked($name) ? ' checked="checked"' : '') ?>
            />
            <label for="<?php echo $this->getHtmlId() . '_' . $name ?>">
                <?php echo $label ?>
            </label>
        </li>
    <?php endforeach;?>
</ul>

Alternative solutions

--In your case, We can use type="select", and then try to add the Default Yesno Source.

<field id="do_something" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1">
    <label>Do something?</label>
    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
    <comment>Do we want to do something?</comment>
</field>

--Also use type="multiselect" if there are many values.

 <field id="do_something" translate="label" type="multiselect" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1">
        <label>Do something?</label>
       <source_model>Vendor\YourModule\Model\Config\Source\Checkbox</source_model>
  </field>