7

I have a fieldset in the admin panel with one parent select (has 5 options) and 2 fields, which should be displayed in case the parent value select will be 3, 4 or 5. I have not found the examples of similar logic in magento and tried to write by analogy with the usual dependence, but it doesn’t work. In my example, the dependent fields are displayed only while choosing the options with the value 5 form the select and are not displayed when choosing 1, 2, 3, or 4.

Full code (block example):

<?php

namespace Siarhey\Test\Block\Adminhtml\Promo\Quote\Edit\Tab;

class Actions extends \Magento\Backend\Block\Widget\Form\Generic implements
    \Magento\Backend\Block\Widget\Tab\TabInterface
{
    /**
     * @param \Magento\Backend\Block\Template\Context $context
     * @param \Magento\Framework\Registry $registry
     * @param \Magento\Framework\Data\FormFactory $formFactory
     * @param array $data
     */
    public function __construct(
        \Magento\Backend\Block\Template\Context $context,
        \Magento\Framework\Registry $registry,
        \Magento\Framework\Data\FormFactory $formFactory,
        array $data = []
    ) {
        parent::__construct($context, $registry, $formFactory, $data);
    }

    public function getTabLabel()
    {
        return __('Actions');
    }

    public function getTabTitle()
    {
        return __('Actions');
    }

    public function canShowTab()
    {
        return true;
    }

    public function isHidden()
    {
        return false;
    }

    protected function _prepareForm()
    {
        $model = $this->_coreRegistry->registry('current_promo_quote_rule');

        /** @var \Magento\Framework\Data\Form $form */
        $form = $this->_formFactory->create();
        $form->setHtmlIdPrefix('rule_');

        $fieldset = $form->addFieldset(
            'action_fieldset',
            ['legend' => __('Rules')]
        );

        $parentField = $fieldset->addField(
            'simple_action',
            'select',
            [
                'label' => __('Apply'),
                'name' => 'simple_action',
                'options' => [
                    1 => __('Amount 1'),
                    2 => __('Discount 1'),
                    3 => __('Amount 2'),
                    4 => __('Discount 2'),
                ]
            ]
        );

        $childFieldOne = $fieldset->addField(
            'amount',
            'text',
            [
                'name' => 'amount',
                'required' => true,
                'class' => 'validate-not-negative-number',
                'label' => __('Amount')
            ]
        );
        $model->setAmount($model->getAmount() * 1);

        $childFieldTwo = $fieldset->addField(
            'percent',
            'text',
            ['name' => 'percent', 'label' => __('Percent')]
        );
        $model->setPercent($model->getPercent() * 1);

        $this->setChild(
            'form_after',
            $this->getLayout()->createBlock(
                'Magento\Backend\Block\Widget\Form\Element\Dependence'
            )->addFieldMap(
                $parentField->getHtmlId(),
                $parentField->getName()
            )->addFieldMap(
                $childFieldOne->getHtmlId(),
                $childFieldOne->getName()
            )->addFieldMap(
                $childFieldTwo->getHtmlId(),
                $childFieldTwo->getName()
            )->addFieldDependence(
                $childFieldOne->getName(),
                $parentField->getName(),
                '1,3'
            )->addFieldDependence(
                $childFieldTwo->getName(),
                $parentField->getName(),
                '2,4'
            )
        );

        $form->setValues($model->getData());

        if ($model->isReadonly()) {
            foreach ($fieldset->getElements() as $element) {
                $element->setReadonly(true, true);
            }
        }

        $this->setForm($form);
        return parent::_prepareForm();
    }
}

Result (view):

Option 1
Option 1 selected

Option 4 Option 4 selected

Without dependency Without dependency

Code sample 1 (doesn’t work):

/*
 * $parentField is select with values (0,1,2,3,4,5)
 */
$this->setChild(
    'form_after',
    $this->getLayout()->createBlock(
        'Magento\Backend\Block\Widget\Form\Element\Dependence'
    )->addFieldMap(
        $parentField->getHtmlId(),
        $parentField->getName()
    )->addFieldMap(
        $childFieldOne->getHtmlId(),
        $childFieldOne->getName()
    )->addFieldDependence(
        $childFieldOne->getName(),
        $parentField->getName(),
        '3'
    )->addFieldDependence(
        $childFieldOne->getName(),
        $parentField->getName(),
        '4'
    )->addFieldDependence(
        $childFieldOne->getName(),
        $parentField->getName(),
        '5'
    )->addFieldMap(
        $parentField->getHtmlId(),
        $parentField->getName()
    )->addFieldMap(
        $childFieldTwo->getHtmlId(),
        $childFieldTwo->getName()
    )->addFieldDependence(
        $childFieldTwo->getName(),
        $parentField->getName(),
        '3'
    )->addFieldDependence(
        $childFieldTwo->getName(),
        $parentField->getName(),
        '4'
    )->addFieldDependence(
        $childFieldTwo->getName(),
        $parentField->getName(),
        '5'
    )
);

Code sample 2 (doesn’t work):

/*
 * $parentField is select with values (0,1,2,3,4,5)
 */
$this->setChild(
    'form_after',
    $this->getLayout()->createBlock(
        'Magento\Backend\Block\Widget\Form\Element\Dependence'
    )->addFieldMap(
        $parentField->getHtmlId(),
        $parentField->getName()
    )->addFieldMap(
        $childFieldOne->getHtmlId(),
        $childFieldOne->getName()
    )->addFieldMap(
        $parentField->getHtmlId(),
        $parentField->getName()
    )->addFieldMap(
        $childFieldTwo->getHtmlId(),
        $childFieldTwo->getName()
    )->addFieldDependence(
        $childFieldOne->getName(),
        $parentField->getName(),
        array('3', '4', '5')
    )->addFieldDependence(
        $childFieldTwo->getName(),
        $parentField->getName(),
        array('3', '4', '5')
    )
);

Result:

Notice: Array to string conversion in /var/www/magento2/app/code/Magento/Backend/Block/Widget/Form/Element/Dependence.php on line 95

UPDATE:


Code sample 3 (doesn't work, if selected value is not '3,4,5'):

// Parent field
$typeField = $fieldset->addField(
    'action_type',
    'select',
    [
        'label' => __('Type'),
        'name' => 'action_type',
        'options' => ['1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '3,4,5' => '3,4,5']
    ]
);

$this->setChild(
    'form_after',
    $this->getLayout()->createBlock(
        'Magento\Backend\Block\Widget\Form\Element\Dependence'
    )->addFieldMap(
        $parentField->getHtmlId(),
        $parentField->getName()
    )->addFieldMap(
        $childFieldOne->getHtmlId(),
        $childFieldOne->getName()
    )->addFieldMap(
        $typeField->getHtmlId(),
        $typeField->getName()
    )->addFieldMap(
        $childFieldTwo->getHtmlId(),
        $childFieldTwo->getName()
    )->addFieldDependence(
        $childFieldOne->getName(),
        $parentField->getName(),
        '3,4,5'
    )->addFieldDependence(
        $childFieldTwo->getName(),
        $parentField->getName(),
        '3,4,5'
    )
);

Has someone faced the same issue and found a solution?

Update:

Maybe someone else can the check the presence of this issue? I’ve checked it on 3 different installations and this solution ( the line with the values coma separated ) still doesn’t work.

6
  • Could you show your full of code, It works for me.
    – Sohel Rana
    Commented Apr 16, 2016 at 8:35
  • @SohelRana I need to reproduce it now (I'm at home). I will be able to publish it approximately in an hour. Thank you for you help :) Commented Apr 16, 2016 at 8:42
  • @SohelRana I've update my question with the code of the block and screenshots (result) Commented Apr 16, 2016 at 12:12
  • Check updated answer.
    – Sohel Rana
    Commented Apr 16, 2016 at 15:11
  • I just check it with M2.0.4, and it's working
    – Sohel Rana
    Commented Apr 16, 2016 at 15:18

6 Answers 6

7
+200

If you check the code that is responsible for adding the corresponding fields in accordance with the dependencies in the file lib/web/mage/adminhtml/form.js, you will see the following scheme there:

    var shouldShowUp = true;
    for (var idFrom in valuesFrom) {
        var from = $(idFrom);
        if (from) {
            var values = valuesFrom[idFrom]['values'];
            var isInArray = values.indexOf(from.value) != -1;
            var isNegative = valuesFrom[idFrom]['negative'];
            if (!from || isInArray && isNegative || !isInArray && !isNegative) {
                shouldShowUp = false;
            }
        }
    }

In case you set comma separated values, for example:

    /** @var \Magento\Backend\Block\Widget\Form\Element\Dependence $blockDependence */
    $blockDependence->addFieldMap(
        $actionType->getHtmlId(),
        $actionType->getName()
    )->addFieldMap(
        $amountField->getHtmlId(),
        $amountField->getName()
    )->addFieldDependence(
        $amountField->getName(),
        $actionType->getName(),
        implode(',', array(
            Rule::ACTION_TYPE_OVERWRITE_COST,
            Rule::ACTION_TYPE_ADD_SURCHARGE,
            Rule::ACTION_TYPE_ENABLE_SM_AND_OVERWRITE_COST
        ))
    );

then while debuging you will see that indexOf is trying to find the existing value in the one-element array that is, in your case, a your comma-separated value. This element can't be found:

enter image description here

A step-by-step output of console.log from the method:

console.log(values);
console.log('Value: '+from.value);
console.log('Is in array: '+isInArray);

To create fields multi dependency, you can use the same coma-separated value, but with some modifications. You will just need the block, that will extend \Magento\Backend\Block\Widget\Form\Element\Dependence:

<?php

namespace Vendor\Module\Block\Widget\Form\Element;

/**
 * Form element dependencies mapper
 * Assumes that one element may depend on other element values.
 * Will toggle as "enabled" only if all elements it depends from toggle as true.
 */
class Dependence extends \Magento\Backend\Block\Widget\Form\Element\Dependence
{
    /**
     * @param \Magento\Backend\Block\Context $context
     * @param \Magento\Framework\Json\EncoderInterface $jsonEncoder
     * @param \Magento\Config\Model\Config\Structure\Element\Dependency\FieldFactory $fieldFactory
     * @param array $data
     */
    public function _construct(
        \Magento\Backend\Block\Context $context,
        \Magento\Framework\Json\EncoderInterface $jsonEncoder,
        \Magento\Config\Model\Config\Structure\Element\Dependency\FieldFactory $fieldFactory,
        array $data = []
    )
    {
        parent::_construct($context, $jsonEncoder, $fieldFactory, $data);
    }

    /**
     * {@inheritdoc}
     */
    protected function _toHtml()
    {
        if (!$this->_depends) {
            return '';
        }

        return '<script>
                require(["uiRegistry", "mage/adminhtml/form"], function(registry) {
                    var controller = new FormElementDependenceController(' . $this->_getDependsJson() .
        ($this->_configOptions ? ', ' .
            $this->_jsonEncoder->encode(
                $this->_configOptions
            ) : '') . ');
                    registry.set("formDependenceController", controller);
                });</script>';
    }

    /**
     * Field dependences JSON map generator * @return string
     */
    protected function _getDependsJson()
    {
        $result = [];
        foreach ($this->_depends as $to => $row) {
            foreach ($row as $from => $field) {
                $values = $this->_prepareValues($field->getValues());
                /** @var $field \Magento\Config\Model\Config\Structure\Element\Dependency\Field */
                $result[$this->_fields[$to]][$this->_fields[$from]] = [
                    'values' => $values,
                    'negative' => $field->isNegative(),
                ];
            }
        }
        return $this->_jsonEncoder->encode($result);
    }

    /**
     * @param $values
     * @return array
     */
    protected function _prepareValues($values)
    {
        if (!is_array($values)) {
            return $values;
        }

        $result = array();
        foreach ($values as $value) {
            if (stripos($value, ',')) {
                $result += explode(',', $value);
            } else {
                $result += $value;
            }
        }

        return $result;
    }
}

As you can see, the value is FORCED to be changed into a 1-value array.

The main problem lies in the addFieldDependence of the class \Magento\Backend\Block\Widget\Form\Element\Dependence:

The point is that the value (the line from the dependency), is transferred as the only element of the array. indexOf tries to find the value of the corresponding chosen option, but fails to locate the exact match. As a result, it returns 'false'.

There's also no way to transfer the values as an array, as PHP returns the Notice: Array to string conversion because of the converting of 'value' => (string)$refField.

In our example, we have recreated the one-element array into a multi-element one, where each element consists of several dependencies.

The code of your dependence should be modified (you'll need to change the block). This is how to:

// Dependency START
/** @var \Magento\Backend\Block\Widget\Form\Element\Dependence $blockDependence */
$blockDependence = $this->getLayout()->createBlock(
// 'Magento\Backend\Block\Widget\Form\Element\Dependence'
'{Vendor}\{Module}\Block\Widget\Form\Element\Dependence'
);

$blockDependence->addFieldMap(
    $parentField->getHtmlId(),
    $parentField->getName()
)->addFieldMap(
    $childFieldOne->getHtmlId(),
    $childFieldOne->getName()
)->addFieldMap(
    $childFieldTwo->getHtmlId(),
    $childFieldTwo->getName()
)->addFieldDependence(
    $childFieldOne->getName(),
    $parentField->getName(),
    '1,3'
)->addFieldDependence(
    $childFieldTwo->getName(),
    $parentField->getName(),
    '2,4'
);

$this->setChild('form_after', $blockDependence);
// Dependency END

The result should look like this:

enter image description here

UPD

If you are sure that you will be using a comma separated value in the future, the best way will be to adding the const UNIQUE_DELIMITER with the required value of the delimiter to the class Vendor\Module\Block\Widget\Form\Element\Dependence: E.g.

const UNIQUE_DELIMITER = '~#!~';

Next, modify the partition method:

/**
 * @param $values
 * @return array
 */
protected function _prepareValues($values)
{
    if (!is_array($values)) {
        return $values;
    }

    $result = array();
    foreach ($values as $value) {
        if (stripos($value, self::UNIQUE_DELIMITER)) {
            $result += explode(self::UNIQUE_DELIMITER, $value);
        } else {
            $result += $value;
        }
    }

    return $result;
}

Then, use Vendor\Module\Block\Widget\Form\Element\Dependence::UNIQUE_DELIMITER in your class Actions.

For your convenience, add the class Dependence (after namespace):

use Vendor\Module\Block\Widget\Form\Element\Dependence;

And write the code this way:

 // Dependency START
    /** @var \Magento\Backend\Block\Widget\Form\Element\Dependence $blockDependence */
    $blockDependence = $this->getLayout()->createBlock(
    // 'Magento\Backend\Block\Widget\Form\Element\Dependence'
        '{Vendor}\{Module}\Block\Widget\Form\Element\Dependence'
    );

    $childFieldOneToParentValues = implode(Dependence::UNIQUE_DELIMITER, array('1','3'));
    $childFieldTwoToParentValues = implode(Dependence::UNIQUE_DELIMITER, array('2','4'));

    $blockDependence->addFieldMap(
        $parentField->getHtmlId(),
        $parentField->getName()
    )->addFieldMap(
        $childFieldOne->getHtmlId(),
        $childFieldOne->getName()
    )->addFieldMap(
        $childFieldTwo->getHtmlId(),
        $childFieldTwo->getName()
    )->addFieldDependence(
        $childFieldOne->getName(),
        $parentField->getName(),
        $childFieldOneToParentValues
    )->addFieldDependence(
        $childFieldTwo->getName(),
        $parentField->getName(),
        $childFieldTwoToParentValues
    );

    $this->setChild('form_after', $blockDependence);
    // Dependency END
6
  • 2
    Nice explanation, I'm just wondering what would happen if one of the value is a string containing a comma let's say "15,50" ? Will the code handle that as two values still or properly handle it as one value ? Commented Apr 19, 2016 at 8:44
  • @RaphaelatDigitalPianism Reasonable question :) Commented Apr 20, 2016 at 7:13
  • Thank you for your answer! I'll try to implement this solution today and check it. It will be great, if you can update your answer with solution for the Raphael case. Commented Apr 20, 2016 at 7:13
  • @RaphaelatDigitalPianism we've updated our response (sorry, first we mistakenly posted it into the wrong thread). :)
    – Mageworx
    Commented Apr 20, 2016 at 11:44
  • @MageWorx Now I have checked it and it works. Thank you for your help :) Commented Apr 23, 2016 at 7:21
2

I may be wrong but unfortunately I don't think you can with the default Magento\Backend\Block\Widget\Form\Element\Dependence class.

Let me explain:

The addFieldDependence method looks like this:

public function addFieldDependence($fieldName, $fieldNameFrom, $refField)
{
    if (!is_object($refField)) {
        /** @var $refField \Magento\Config\Model\Config\Structure\Element\Dependency\Field */
        $refField = $this->_fieldFactory->create(
            ['fieldData' => ['value' => (string)$refField], 'fieldPrefix' => '']
        );
    }
    $this->_depends[$fieldName][$fieldNameFrom] = $refField;
    return $this;
}

So let's say you try this code:

addFieldDependence($child,$parent,'2,4')

The value of the $refField will be the following string: 2,4 so as there is no such value in your select it will never work.

If you try this code:

addFieldDependence($child,$parent,array('2,4'))

You will get the Array to string conversion error because of the (string)$refField code

If you try this code:

addFieldDependence($child,$parent,'2')->addFieldDependence($child,$parent,'4')

The first call will set the $refField with the value 2 and assign it to the dependencies using the following code:

$this->_depends[$fieldName][$fieldNameFrom] = $refField;

However, the second code will overwrite that dependency because the $fieldName and $fieldNameFrom variables are the same than during the first call.

What solutions do you have ?

  • Use preferences or plugins to modify the behavior of the Magento\Backend\Block\Widget\Form\Element\Dependence class

The important methods to look at here are addFieldDependence and _getDependsJson. Problem here is that there's a lot of chances that you also may have to modify the JavaScript FormElementDependenceController class that handles the dependencies.

  • Use several duplicate fields with different names: well this is dirty but I reckon that would work.

Example:

    $parentField = $fieldset->addField(
        'simple_action',
        'select',
        [
            'label' => __('Apply'),
            'name' => 'simple_action',
            'options' => [
                1 => __('Amount 1'),
                2 => __('Discount 1'),
                3 => __('Amount 2'),
                4 => __('Discount 2'),
            ]
        ]
    );

    $childFieldOne = $fieldset->addField(
        'amount',
        'text',
        [
            'name' => 'amount',
            'required' => true,
            'class' => 'validate-not-negative-number',
            'label' => __('Amount')
        ]
    );

    $childFieldOneCopy = $fieldset->addField(
        'amount',
        'text',
        [
            'name' => 'amount',
            'required' => true,
            'class' => 'validate-not-negative-number',
            'label' => __('Amount')
        ]
    );

Then use:

->addFieldDependence(
            $childFieldOne->getName(),
            $parentField->getName(),
            '1'
        )    
->addFieldDependence(
            $childFieldOneCopy->getName(),
            $parentField->getName(),
            '3'
        )

The problem here is that you will have to add several checks in the controller that processes the data to ensure you're processing the right data and not the hidden copy field.

2
  • Thank you for your answer. Now it’s clearer. What solution is better in your opinion: use several fields or create own Form\Element\Dependence block? Commented Apr 20, 2016 at 7:20
  • 1
    @SiarheyUchukhlebau as I said in my answer, creating your own Form\Element\Dependence is the way it should be done. The problem is that it may take you a long time to develop such feature as you have a lot of different files to deal with. The several fields solution is dirtier but I'm pretty sure using that method you could get what you want quickly and it will work without potentially breaking anything Commented Apr 20, 2016 at 7:32
2

I think that you should look class Magento\Backend\Block\Widget\Form\Element\Dependence. You can create your own block inherited from this class and rewrite it how you want. In your code replaces block call:

$this->getLayout()->createBlock(
    'Magento\Backend\Block\Widget\Form\Element\Dependence'
)

to:

this->getLayout()->createBlock(
    'Siarhey\Test\Block\Widget\Form\Element\Dependence'
)

Create block Siarhey\Test\Block\Widget\Form\Element\Dependence and you can implement in it your logic of verification.

It is only advice. I hope that it helps you.

2

Create a di.xml under adminhtml and add following code:

Basically need to overwrite Magento\Backend\Block\Widget\Form\Element\Dependence class


<preference for="Magento\Backend\Block\Widget\Form\Element\Dependence"
                type="Vendor\Module\Block\Widget\Form\Element\Dependence" />


namespace Vendor\Module\Block\Widget\Form\Element;

class Dependence extends \Magento\Backend\Block\Widget\Form\Element\Dependence
{
    /**
     * Register field name dependence one from each other by specified values
     *
     * @param string $fieldName
     * @param string $fieldNameFrom
     * @param \Magento\Config\Model\Config\Structure\Element\Dependency\Field|string $refField
     * @return \Magento\Backend\Block\Widget\Form\Element\Dependence
     */
    public function addFieldDependence($fieldName, $fieldNameFrom, $refField)
    {
        if (!is_object($refField)) {
            /** @var $refField \Magento\Config\Model\Config\Structure\Element\Dependency\Field */
            $refField = $this->_fieldFactory->create(
                ['fieldData' => ['value' => (string)$refField, 'separator' => ','], 'fieldPrefix' => '']
            );
        }
        $this->_depends[$fieldName][$fieldNameFrom] = $refField;
        return $this;
    }
}

Now you can use following way.


->addFieldDependence(
    $childFieldTwo->getName(),
    $parentField->getName(),
    '2,4'
)

Clear Magento2 cache.

5
  • Thank you for your answer, but still doesn't work, if selected value is not '3,4,5' (string with commas) Commented Apr 11, 2016 at 16:17
  • if selected value is any one of this string '3,4,5', then it should work.
    – Sohel Rana
    Commented Apr 11, 2016 at 16:21
  • You need to use comma separated string instead of array.
    – Sohel Rana
    Commented Apr 11, 2016 at 16:26
  • I tested and it does not work if the selected value 3 or 4 or 5 :( But it works if the selected value is '3,4,5' (string) Commented Apr 11, 2016 at 16:27
  • show your full code for this file only
    – Sohel Rana
    Commented Apr 11, 2016 at 16:37
1

I'm not sure about this, I haven't tested it, but looking at the addFieldDependence method and at \Magento\Config\Model\Config\Structure\Element\Dependency\Field class I think it might work.

Add this in your class:

protected $fieldFactory;
public function __construct(
   ....
   \Magento\Config\Model\Config\Structure\Element\Dependency\FieldFactory $fieldFactory,
   ....
) {
    $this->fieldFactory = $fieldFactory;
}

Then, instead of

->addFieldDependence(
    $childFieldOne->getName(),
    $parentField->getName(),
    '1,3'
)

Try this:

$someField = $this->fieldFactory()->create([
    'fieldData' => [
         'separator' => ',',
          'value' => '1,3',
    ],
    'fieldPrefix' => ''
]);

->addFieldDependence(
    $childFieldOne->getName(),
    $parentField->getName(),
    $someField
)
1

Define $this->_fieldFactory and try below one, its Working for me:

    $blockDependence = $this->getLayout()->createBlock(
        'Magento\Backend\Block\Widget\Form\Element\Dependence'
    );

    $filter = $this->_fieldFactory->create([
        'fieldData' => [
            'separator' => ',',
            'value' => '0,1',
        ],
        'fieldPrefix' => '',
    ]);

    $blockDependence->addFieldMap(
        "discount_type",
        'discount_type'
    )->addFieldMap(
        "discount",
        'discount'
    )->addFieldDependence(
        'discount',
        'discount_type',
        $filter
    );

    $this->setChild('form_after', $blockDependence);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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