Comment rendre les champs du fieldset multi-dépendants ?
-
29-09-2020 - |
Question
j'ai un ensemble de champs dans le panneau d'administration avec une sélection de parent (a 5 options) et 2 des champs, qui doit être affiché au cas où la valeur parent sélectionnée serait 3, 4 ou 5.Je n'ai pas trouvé d'exemples de logique similaire dans magento et j'ai essayé d'écrire par analogie avec la dépendance habituelle, mais ça ne marche pas.Dans mon exemple, les champs dépendants sont affichés uniquement lors du choix des options avec la valeur 5 lors de la sélection et ne sont pas affichés lors du choix de 1, 2, 3 ou 4.
Code complet (exemple de bloc) :
<?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();
}
}
Résultat (vue) :
Exemple de code 1 (ne fonctionne pas) :
/*
* $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'
)
);
Exemple de code 2 (ne fonctionne pas) :
/*
* $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')
)
);
Résultat:
Avis:Conversion du tableau en chaîne dans /var/www/magento2/app/code/magento/backend/block/widget/form/element/dependence.php sur la ligne 95
MISE À JOUR:
Exemple de code 3 (ne fonctionne pas si la valeur sélectionnée n'est pas
'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'
)
);
Quelqu'un a-t-il rencontré le même problème et trouvé une solution ?
Mise à jour:
Peut-être que quelqu'un d'autre peut vérifier la présence de ce problème ?Je l'ai vérifié sur 3 installations différentes et cette solution (la ligne avec les valeurs séparées par des virgules) ne fonctionne toujours pas.
La solution
Si vous cochez le code qui se charge d'ajouter les champs correspondants conformément aux dépendances dans le fichier lib/web/mage/adminhtml/form.js
, vous y verrez le schéma suivant :
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;
}
}
}
Si vous définissez des valeurs séparées par des virgules, par exemple :
/** @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
))
);
puis lors du débogage, vous verrez que indexOf
essaie de trouver la valeur existante dans le tableau à un élément qui est, dans votre cas, une valeur séparée par des virgules.Cet élément est introuvable :
Une sortie étape par étape de console.log
de la méthode :
console.log(values);
console.log('Value: '+from.value);
console.log('Is in array: '+isInArray);
Pour créer des champs multi-dépendances, vous pouvez utiliser la même valeur séparée par des virgules, mais avec quelques modifications.Vous aurez juste besoin du bloc, qui s'étendra \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;
}
}
Comme vous pouvez le voir, la valeur est FORCÉE pour être transformée en un tableau à 1 valeur.
Le principal problème réside dans addFieldDependence
de la classe \Magento\Backend\Block\Widget\Form\Element\Dependence
:
Le fait est que la valeur (la ligne de la dépendance) est transférée comme seul élément du tableau. indexOf
essaie de trouver la valeur de l'option choisie correspondante, mais ne parvient pas à localiser la correspondance exacte.En conséquence, il renvoie « faux ».
Il n'y a également aucun moyen de transférer les valeurs sous forme de tableau, car PHP renvoie le Notice: Array to string conversion
à cause de la conversion de 'value' => (string)$refField
.
Dans notre exemple, nous avons recréé le tableau à un élément en un tableau à plusieurs éléments, où chaque élément est constitué de plusieurs dépendances.
Le code de votre dépendance doit être modifié (il faudra changer de bloc).Voici comment :
// 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
Le résultat devrait ressembler à ceci :
MISE À JOUR
Si vous êtes sûr d'utiliser une valeur séparée par des virgules à l'avenir, la meilleure façon sera d'ajouter la const UNIQUE_DELIMITER
avec la valeur requise du délimiteur de la classe Vendor\Module\Block\Widget\Form\Element\Dependence
:Par exemple.
const UNIQUE_DELIMITER = '~#!~';
Ensuite, modifiez la méthode de partition :
/**
* @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;
}
Ensuite, utilisez Vendor\Module\Block\Widget\Form\Element\Dependence::UNIQUE_DELIMITER
Dans votre classe Actions
.
Pour votre commodité, ajoutez la classe Dependence
(après l'espace de noms) :
use Vendor\Module\Block\Widget\Form\Element\Dependence;
Et écrivez le code de cette façon :
// 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
Autres conseils
Je me trompe peut-être mais malheureusement, je ne pense pas que vous puissiez le faire avec la valeur par défaut Magento\Backend\Block\Widget\Form\Element\Dependence
classe.
Laisse-moi expliquer:
Le addFieldDependence
la méthode ressemble à ceci :
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;
}
Supposons que vous essayiez ce code :
addFieldDependence($child,$parent,'2,4')
Le value
de la $refField
sera la chaîne suivante : 2,4
donc comme il n'y a pas une telle valeur dans votre sélection, cela ne fonctionnera jamais.
Si vous essayez ce code :
addFieldDependence($child,$parent,array('2,4'))
Vous obtiendrez le Array to string conversion
erreur à cause du (string)$refField
code
Si vous essayez ce code :
addFieldDependence($child,$parent,'2')->addFieldDependence($child,$parent,'4')
Le premier appel définira le $refField
avec la valeur 2 et affectez-le aux dépendances à l'aide du code suivant :
$this->_depends[$fieldName][$fieldNameFrom] = $refField;
Cependant, le deuxième code écrasera cette dépendance car le $fieldName
et $fieldNameFrom
les variables sont les mêmes que lors du premier appel.
Quelles solutions avez-vous ?
- Utilisez des préférences ou des plugins pour modifier le comportement du
Magento\Backend\Block\Widget\Form\Element\Dependence
classe
Les méthodes importantes à examiner ici sont addFieldDependence
et _getDependsJson
.Le problème ici est qu'il y a de fortes chances que vous deviez également modifier le code JavaScript. FormElementDependenceController
classe qui gère les dépendances.
- Utilisez plusieurs champs en double avec des noms différents :eh bien, c'est sale mais je pense que cela fonctionnerait.
Exemple:
$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')
]
);
Utilisez ensuite :
->addFieldDependence(
$childFieldOne->getName(),
$parentField->getName(),
'1'
)
->addFieldDependence(
$childFieldOneCopy->getName(),
$parentField->getName(),
'3'
)
Le problème ici est que vous devrez ajouter plusieurs vérifications dans le contrôleur qui traite les données pour garantir que vous traitez les bonnes données et non le champ de copie masquée.
Je pense que tu devrais avoir l'air classe Magento\Backend\Block\Widget\Form\Element\Dependence
.Vous pouvez créer votre propre bloc hérité de cette classe et le réécrire comme vous le souhaitez.Dans votre code remplace l'appel de bloc :
$this->getLayout()->createBlock(
'Magento\Backend\Block\Widget\Form\Element\Dependence'
)
à:
this->getLayout()->createBlock(
'Siarhey\Test\Block\Widget\Form\Element\Dependence'
)
Créer un bloc Siarhey\Test\Block\Widget\Form\Element\Dependence
et vous pouvez y implémenter votre logique de vérification.
Ce n'est qu'un conseil.J'espère que cela vous aidera.
Créez un di.xml sous adminhtml et ajoutez le code suivant :
En gros, il faut écraser Magento\Backend\Block\Widget\Form\Element\Dépendance classe
<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;
}
}
Vous pouvez maintenant utiliser la méthode suivante.
->addFieldDependence(
$childFieldTwo->getName(),
$parentField->getName(),
'2,4'
)
Videz le cache Magento2.
Je n'en suis pas sûr, je ne l'ai pas testé, mais en regardant le addFieldDependence
méthode et à \Magento\Config\Model\Config\Structure\Element\Dependency\Field
cours, je pense que ça pourrait marcher.
Ajoutez ceci dans votre classe :
protected $fieldFactory;
public function __construct(
....
\Magento\Config\Model\Config\Structure\Element\Dependency\FieldFactory $fieldFactory,
....
) {
$this->fieldFactory = $fieldFactory;
}
Ensuite, au lieu de
->addFieldDependence(
$childFieldOne->getName(),
$parentField->getName(),
'1,3'
)
Essaye ça:
$someField = $this->fieldFactory()->create([
'fieldData' => [
'separator' => ',',
'value' => '1,3',
],
'fieldPrefix' => ''
]);
->addFieldDependence(
$childFieldOne->getName(),
$parentField->getName(),
$someField
)
Définir $this->_fieldFactory
et essayez-en un ci-dessous, cela fonctionne pour moi :
$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);