How to make the fields of the fieldset multidependable?
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:
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:
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
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.
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.