Magento 2.1 Comment créer un champ de composant de formulaire personnalisé dépend d'une autre valeur de champ?

13

J'ai un choix de champ qui a quelques options. L'un d'eux aura certains champs dépendants de la valeur, un autre champ sera masqué. J'ai copié et étendu le composant js pour mon domaine mais cela n'a pas fonctionné ou je l'ai mal fait. Le composant Ui prend en charge cette fonctionnalité? Comment puis-je y parvenir?

Voici ce que j'ai fait:

<field name="field1">
    <argument name="data" xsi:type="array">
        <item name="options" xsi:type="object">Namespace\ModuleName\Model\Config\Source\Options</item>
        <item name="config" xsi:type="array">
            <item name="label" xsi:type="string" translate="true">Field name</item>
            <item name="visible" xsi:type="boolean">true</item>
            <item name="dataType" xsi:type="string">number</item>
            <item name="formElement" xsi:type="string">select</item>
            <item name="source" xsi:type="string">item</item>
            <item name="dataScope" xsi:type="string">field1</item>
            <item name="component" xsi:type="string">Pathto/js/form/element/options</item>
            <item name="validation" xsi:type="array">
                <item name="required-entry" xsi:type="boolean">true</item>
            </item>
        </item>
    </argument>
</field>

<field name="field2Depend1"></field>
<field name="field3Depend1"></field>

jsComponent js/form/element/options:

define([
    'underscore',
    'uiRegistry',
    'Magento_Ui/js/form/element/select',
    'Magento_Ui/js/modal/modal'
], function (_, uiRegistry, select) {
    'use strict';

    return select.extend({

        onChange: function () {
            this.enableDisableFields();
        },

        /**
         * Enable/disable fields on Coupons tab
         */
        enableDisableFields: function () {
            // code check field
        }
    });
});
mrtuvn
la source

Réponses:

26

Essayez ceci ( Remarque : N'oubliez pas de remplacer la ligne "Namespace" et la ligne "ModuleName" par vos valeurs):

<field name="field1">
    <argument name="data" xsi:type="array">
        <item name="options" xsi:type="object">Namespace\ModuleName\Model\Config\Source\Options</item>
        <item name="config" xsi:type="array">
            <item name="label" xsi:type="string" translate="true">Parent Option</item>
            <item name="component" xsi:type="string">Namespace_ModuleName/js/form/element/options</item>
            <item name="visible" xsi:type="boolean">true</item>
            <item name="dataType" xsi:type="string">number</item>
            <item name="formElement" xsi:type="string">select</item>
            <item name="source" xsi:type="string">item</item>
            <item name="dataScope" xsi:type="string">field1</item>
            <item name="sortOrder" xsi:type="number">210</item>
            <item name="validation" xsi:type="array">
                <item name="required-entry" xsi:type="boolean">true</item>
            </item>
        </item>
    </argument>
</field>

<field name="field2Depend1">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="label" xsi:type="string">Field 1</item>
            <item name="dataType" xsi:type="string">text</item>
            <item name="formElement" xsi:type="string">input</item>
            <item name="source" xsi:type="string">item</item>
            <item name="sortOrder" xsi:type="number">220</item>
            <item name="breakLine" xsi:type="boolean">true</item>
            <item name="visibleValue" xsi:type="string">2</item>
            <item name="visible" xsi:type="boolean">false</item>
        </item>
    </argument>
</field>
<field name="field3Depend1">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="label" xsi:type="string">Field 2</item>
            <item name="dataType" xsi:type="string">text</item>
            <item name="formElement" xsi:type="string">input</item>
            <item name="source" xsi:type="string">item</item>
            <item name="sortOrder" xsi:type="number">230</item>
            <item name="breakLine" xsi:type="boolean">true</item>
            <item name="visibleValue" xsi:type="string">0</item>
            <item name="visible" xsi:type="boolean">false</item>
        </item>
    </argument>
</field>

Où:

  • La visibilité des éléments enfants est définie par défaut comme false;
  • Le visibleValue- est la field1valeur lorsque l'élément doit être visible;

Espace de noms \ ModuleName \ Model \ Config \ Source \ Options

namespace Namespace\ModuleName\Model\Config\Source;

use Magento\Framework\Option\ArrayInterface;

class Options implements ArrayInterface
{
    /**
     * @return array
     */
    public function toOptionArray()
    {
        $options = [
            0 => [
                'label' => 'Please select',
                'value' => 0
            ],
            1 => [
                'label' => 'Option 1',
                'value' => 1
            ],
            2  => [
                'label' => 'Option 2',
                'value' => 2
            ],
            3 => [
                'label' => 'Option 3',
                'value' => 3
            ],
        ];

        return $options;
    }
}

app / code / Espace de noms / ModuleName / view / adminhtml / web / js / form / element / options.js

define([
    'underscore',
    'uiRegistry',
    'Magento_Ui/js/form/element/select',
    'Magento_Ui/js/modal/modal'
], function (_, uiRegistry, select, modal) {
    'use strict';

    return select.extend({

        /**
         * On value change handler.
         *
         * @param {String} value
         */
        onUpdate: function (value) {
            console.log('Selected Value: ' + value);

            var field1 = uiRegistry.get('index = field2Depend1');
            if (field1.visibleValue == value) {
                field1.show();
            } else {
                field1.hide();
            }

            var field2 = uiRegistry.get('index = field3Depend1');
            if (field2.visibleValue == value) {
                field2.show();
            } else {
                field2.hide();
            }

            return this._super();
        },
    });
});

Résultat:

Valeur 0 sélectionnée: Valeur 0 sélectionnée

Valeur 1 sélectionnée: Valeur 1 sélectionnée

Valeur 2 sélectionnée: Valeur 2 sélectionnée

Valeur 3 sélectionnée: Valeur 3 sélectionnée

PS: Ce n'est peut-être pas la meilleure solution, mais cela vous aidera

Siarhey Uchukhlebau
la source
onUpdate fonctionne bien, mais comment faire onLoad? Comment obtenir field1.value?
zhartaunik
@zhartaunik Je pense que vous devriez utiliser la initializeméthode dans votre cas car ui-element n'a pas de onLoadméthode. Vous pouvez obtenir une valeur de champ en tout lieu du registre en utilisant l'index entrée clé: uiRegistry.get('index = field1'). Si vous avez plus de questions, veuillez m'adresser via skype (sarj1989), il sera plus facile de communiquer en russe.
Siarhey Uchukhlebau
Merci @Siarhey. J'ai décidé d'utiliser initialize. this._super, que d'ajouter la vérification nécessaire.
zhartaunik
1
Je ne peux pas obtenir la valeur du champ lorsque j'utilise la valeur de la méthode d'initialisation est "non défini".
Saurabh Taletiya
1
@Siarhey Uchukhlebau Puis-je ajouter une case à cocher à la place?
Juliano Vargas
8

La solution suggérée par Magentix générera de temps en temps une erreur lors de l'utilisation de l'initialisation. Cela dépend du temps nécessaire à votre navigateur pour rendre les composants. Pour y remédier, vous pouvez utiliser setTimeout.

Voir le code ci-dessous:

define([
    'underscore',
    'uiRegistry',
    'Magento_Ui/js/form/element/select',
    'Magento_Ui/js/modal/modal'
], function (_, uiRegistry, select, modal) {
    'use strict';

    return select.extend({

        /**
         * Extends instance with defaults, extends config with formatted values
         *     and options, and invokes initialize method of AbstractElement class.
         *     If instance's 'customEntry' property is set to true, calls 'initInput'
         */
        initialize: function () {
            this._super();

            this.resetVisibility();

            return this;
        },

        toggleVisibilityOnRender: function (visibility, time) {
            var field = uiRegistry.get('index = field_to_toggle');
            if(field !== undefined) {
                if(visibility == 1) {
                    field.show();
                } else {
                    field.hide();
                }

                return;
            }
            else {
                var self = this;
                setTimeout(function() {
                    self.toggleVisibilityOnRender(visibility, time);
                }, time);
            }
        },

        /**
         * On value change handler.
         *
         * @param {String} value
         */
        onUpdate: function (value) {
            if (value == 1) {
                this.showField();
            } else {
                this.hideField();
            }
            return this._super();
        },

        resetVisibility: function () {
            if (this.value() == 1) {
                this.showField();
            } else {
                this.hideField();
            }
        },

        showField: function () {
            this.toggleVisibilityOnRender(1, 1000);

        },

        hideField: function () {
            this.toggleVisibilityOnRender(0, 1000);
        }
    });
});
Mageinn
la source
Son fonctionne correctement.
Dhaduk Mitesh
+1 de mon côté. Aucun autre travail mais cela a fait mon travail.
anonyme
7

C'est une vieille question avec plusieurs réponses qui fonctionnent, mais j'ai découvert une solution utilisant ce que Magento fournit (à partir de 2.1.0) sans avoir besoin d'étendre les composants. Comme plusieurs questions ont été marquées comme étant en double et dirigées ici, j'ai pensé qu'il serait utile de fournir des informations sur cette option.

Tous les composants ui de l'élément de formulaire qui s'étendent Magento_Ui/js/form/element/abstract.jsont un switcherConfigparamètre disponible à des fins telles que le masquage / affichage des éléments ainsi que d'autres actions. Le switchercomposant peut être trouvé sur Magento_Ui / js / form / switcher pour les curieux. Vous pouvez en trouver des exemples dans sales_rule_form.xml et catalog_rule_form.xml . Bien sûr, si vous utilisez déjà votre propre composant personnalisé, vous pouvez toujours l'utiliser aussi longtemps que votre composant s'étend finalement, abstractce qui semble être le cas en fonction de l'exemple de code fourni dans la question.

Maintenant, pour un exemple plus spécifique pour répondre à la question d'origine.

Dans, Namespace/ModuleName/view/adminhtml/ui_component/your_entity_form.xmlvous devez simplement ajouter ce qui suit aux champs settingsqui font le contrôle (c'est-à-dire le champ qui détermine quels champs sont masqués / visibles). Dans votre exemple, ce serait field1.

<field name="field1">
    <argument name="data" xsi:type="array">
        ...
    </argument>
    <settings>
        <switcherConfig>
            <rules>
                <rule name="0">
                    <value>2</value>
                    <actions>
                        <action name="0">
                            <target>your_entity_form.your_entity_form.entity_information.field2Depend1</target>
                            <callback>show</callback>
                        </action>
                        <action name="1">
                            <target>your_entity_form.your_entity_form.entity_information.field3Depend1</target>
                            <callback>hide</callback>
                        </action>
                    </actions>
                </rule>
                <rule name="1">
                    <value>3</value>
                    <actions>
                        <action name="0">
                            <target>your_entity_form.your_entity_form.entity_information.field2Depend1</target>
                            <callback>hide</callback>
                        </action>
                        <action name="1">
                            <target>your_entity_form.your_entity_form.entity_information.field3Depend1</target>
                            <callback>show</callback>
                        </action>
                    </actions>
                </rule>
            </rules>
            <enabled>true</enabled>
        </switcherConfig>
    </settings>
</field>

Décomposons-le un peu. Le switchercomposant contient un tableau rulesdont nous construisons ici. Chacun <rule>a un nom qui est un nombre dans cet exemple. Ce nom est la clé / l'index du tableau pour cet élément. Nous utilisons des nombres comme index de tableau. Les cordes devraient aussi fonctionner mais je n'ai pas testé cette théorie . MISE À JOUR - Comme mentionné par @ChristopheFerreboeuf dans les commentaires, les chaînes ne fonctionnent pas ici. Ce sont des tableaux et doivent commencer par 0, pas des chaînes ou 1.

À l'intérieur de chacun, rulenous passons deux arguments.

  1. value- C'est la valeur field1qui doit déclencher la actionsdéfinition ci-dessous.
  2. actions- Ici, nous avons un autre tableau. Ce sont les actions à déclencher lorsque les conditions de cette règle sont remplies. Encore une fois, actionle nom de chacun n'est que l'index / la clé du tableau de cet élément.

Maintenant, chacun actiona également deux arguments (avec un 3e optionnel).

  1. target- C'est l'élément que vous souhaitez manipuler dans le cadre de cette action. Si vous ne savez pas comment les noms des éléments ui_component sont composés dans Magento, vous pouvez consulter l'article d'Alan Storm . C'est fondamentalement quelque chose comme {component_name}.{component_name}.{fieldset_name}.{field_name}dans cet exemple.
  2. callback- Voici les mesures à prendre dans ce qui précède target. Ce rappel doit être une fonction disponible sur l'élément ciblé. Notre exemple utilise hideet show. C'est là que vous pouvez commencer à développer les fonctionnalités disponibles. L' catalog_rule_form.xmlexemple que j'ai mentionné plus haut utilise setValidationsi vous souhaitez voir un autre exemple.
  3. Vous pouvez également ajouter <params>à tout ce actionqui les appelle. Vous pouvez également le voir dans l' catalog_rule_form.xmlexemple.

Enfin, le dernier élément à l'intérieur switcherConfigest <enabled>true</enabled>. Cela devrait être assez simple, c'est un booléen pour activer / désactiver la fonctionnalité de commutateur que nous venons d'implémenter.

Et nous avons terminé. Donc , en utilisant l'exemple ci - dessus ce que vous devriez voir est le champ field2Depend1affiché si vous choisissez une option avec une valeur 2sur field1, et field3Depend1affiché si vous choisissez une option avec la valeur 3.

J'ai testé cet exemple en utilisant uniquement hideet showsur un champ obligatoire et il semble prendre en compte la visibilité pour la validation. En d'autres termes, si field2Depend1nécessaire, il ne sera requis que lorsqu'il sera visible. Pas besoin de configuration supplémentaire pour que cela fonctionne.

J'espère que cela fournira de l'aide à tous ceux qui recherchent une solution plus prête à l'emploi.

rain2o
la source
1
"Les cordes devraient aussi fonctionner mais je n'ai pas testé cette théorie." J'ai accidentellement testé et ça ne marche pas ... Les actions sont comme un tableau de règles qui doit commencer par l'action 0 ou la règle 0 pas 1 ou une chaîne ...
Christophe Ferreboeuf
6

Il y a beaucoup de réponses à cette question, mais la plupart d'entre elles font des hypothèses sur si l'uRegistry est complètement chargé, ou utilisent setTimeoutpour effacer la pile d'appels, et attendre la prochaine boucle d'événements (qui à mon avis est toujours la mauvaise façon de faites-le - puisque vous ne pouvez pas être sûr quand les autres composants de l'interface utilisateur ont été chargés - corrigez-moi si je me trompe).

Tout d'abord, bien sûr, ajoutez votre composant JS personnalisé à la configuration du champ (voir les autres réponses pour plus de détails):

<item name="component" xsi:type="string">Namespace_ModuleName/js/form/element/options</item>

Ensuite, voici le composant d'interface utilisateur personnalisé qui masque ou affiche les champs dépendants - avec des commentaires pour expliquer ce qui se passe.

define([
    'underscore',
    'uiRegistry',
    'Magento_Ui/js/form/element/select'
], function (_, uiRegistry, select) {

    'use strict';

    return select.extend({

        /**
         * Array of field names that depend on the value of 
         * this UI component.
         */
        dependentFieldNames: [
            'my_field_name1',
            'my_field_name2'
        ],

        /**
         * Reference storage for dependent fields. We're caching this
         * because we don't want to query the UI registry so often.
         */
        dependentFields : [],

        /**
         * Initialize field component, and store a reference to the dependent fields.
         */
        initialize: function() {
            this._super();

            // We're creating a promise that resolves when we're sure that all our dependent
            // UI components have been loaded. We're also binding our callback because
            // we're making use of `this`
            uiRegistry.promise(this.dependentFieldNames).done(_.bind(function() {

                // Let's store the arguments (the UI Components we queried for) in our object
                this.dependentFields = arguments;

                // Set the initial visibility of our fields.
                this.processDependentFieldVisibility(parseInt(this.initialValue));
            }, this));
        },

        /**
         * On value change handler.
         *
         * @param {String} value
         */
        onUpdate: function (value) {
            // We're calling parseInt, because in JS "0" evaluates to True
            this.processDependentFieldVisibility(parseInt(value));
            return this._super();
        },

        /**
         * Shows or hides dependent fields.
         *
         * @param visibility
         */
        processDependentFieldVisibility: function (visibility) {
            var method = 'hide';
            if (visibility) {
                method = 'show';
            }

            // Underscore's invoke, calls the passed method on all the objects in our array
            _.invoke(this.dependentFields, method);
        }
    });
});
Erfan
la source
5

Si vous avez une erreur comme Field is Undefinedlors de l'initialisation de la visibilité des champs, utilisez setTimeout()pour charger les champs dépendants:

fieldDepend: function (value) {
     setTimeout(function(){ 
        var field1 = uiRegistry.get('index = field2');

        if (field1.visibleValue == value) {
               field1.show();
        } else {
               field1.hide();
        }

       var field2 = uiRegistry.get('index = field3');

        if (field2.visibleValue == value) {
              field2.show();
        } else {
              field2.hide();
        }    
     }, 1);
     return this._super();
},
Ronak Chauhan
la source
Au lieu de setTimeout, utilisez plutôt une méthode asynchrone pour obtenir les dépendances:uiRegistry.get('q', function(field) { ... }));
Erfan
Au lieu de suggérer en commentaire et de voter contre ma réponse, vous pouvez poster ici votre réponse bro, ce n'est pas la façon de consacrer une réponse, vous suggérez simplement une manière différente, ma réponse n'est pas fausse. @Erfan. votre vote négatif fait mauvaise impression.
Ronak Chauhan
@RonakChauhan - D'accord sur le point !!! votre réponse n'est pas incorrecte, différentes personnes ont des opinions, des suggestions et des solutions différentes. Votre réponse est également correcte !!
Manthan Dave
Attendre une seconde pour initialiser et bloquer l'initialisation est clairement la mauvaise façon de le faire. Comment savez-vous même que vos dépendances seront chargées en une seconde? Pourquoi ce ne sera pas deux secondes? Vous faites une hypothèse ici, il vaut mieux l'éviter.
Erfan
Je n'ai pas défini 1 seconde ici, c'est en millisecondes, SetTimeout () ne fera que charger mon code après avoir chargé la page, et si vous avez votre réponse, vous pouvez la poster. Voter contre la réponse de quelqu'un n'est pas le moyen de faire ses preuves! @Erfan
Ronak Chauhan
2

Composant personnalisé avec init:

define([
    'underscore',
    'uiRegistry',
    'Magento_Ui/js/form/element/select',
    'Magento_Ui/js/modal/modal'
], function (_, uiRegistry, select, modal) {
    'use strict';

    return select.extend({

        /**
         * Init
         */
        initialize: function () {
            this._super();

            this.fieldDepend(this.value());

            return this;
        },

        /**
         * On value change handler.
         *
         * @param {String} value
         */
        onUpdate: function (value) {
            this.fieldDepend(value);

            return this._super();
        },

        /**
         * Update field dependency
         *
         * @param {String} value
         */
        fieldDepend: function (value) {
            var field = uiRegistry.get('index = field_to_toggle');

            if (value == 'xxxxx') {
                field.show();
            } else {
                field.hide();
            }

            return this;
        }
    });
});
Magentix
la source
c'est montrer "le champ n'est pas défini" après avoir utilisé la fonction d'initialisation.
Prince Patel
1
Utiliser setTimeout()dans fieldDepend()car dépendu n'est pas encore chargé.
Ronak Chauhan
1

Juste au cas où quelqu'un se débat avec la solution Erfan , vous devez passer le chemin complet vers les champs dependentFieldNames, par exemple:

       dependentFieldNames: [
        'form_name.form_name.fieldset.field_name',
        'form_name.form_name.fieldset.field_name1',
        'form_name.form_name.fieldset.field_name2',
        'form_name.form_name.fieldset.field_name3'
    ],

Je ne sais pas pourquoi form_name doit être 2 fois, mais cela a fonctionné pour moi.

Pour déboguer cela, je mets console.log(query);en static/adminhtml/Magento/backend/en_US/Magento_Ui/js/lib/registry/registry.js223e ligne (la fonction get () juste avant this._addRequest(query, callback))

user3722573
la source
1

Il existe plusieurs façons de gérer les dépendances de champs, pour une simple liste déroulante Oui / Non, une case à cocher ou un commutateur, vous pouvez utiliser les propriétés de liaison importsou exportsdans Magento 2. La solution est discutée en détail ici: Champs dépendants dans les formulaires de composants d'interface utilisateur dans Magento 2 sans Javascript pour les champs booléens :

<!-- In the parent field <settings>...</settings> -->
<exports>
    <link name="checked">${$.parentName}.description:disabled</link>
</exports>

<!-- or -->

<!-- In the dependent field <settings>...</settings> -->
<imports>
    <link name="disabled">${$.parentName}.is_active:checked</link>
</imports>

Pour gérer d'autres types de valeurs telles que la dépendance à une liste de valeurs dans une liste déroulante ou bien que peu probable, une valeur d'un champ de saisie, vous pouvez utiliser le switcherConfig. Vérifiez les champs dépendants dans les formulaires à composants ui dans Magento 2 sans Javascript pour plus d'informations.

<switcherConfig>
    <rules>
        <rule name="0">
            <value>list</value><!-- Actions defined will be trigger when the current selected field value matches the value defined here-->
            <actions>
                <action name="0">
                    <target>hs_xml_dependentfield_model_form.hs_xml_dependentfield_model_form.general.list</target>
                    <callback>visible</callback>
                    <params>
                        <param name="0" xsi:type="boolean">true</param>
                    </params>
                </action>
                <action name="1">
                    <target>hs_xml_dependentfield_model_form.hs_xml_dependentfield_model_form.general.hex_code</target>
                    <callback>visible</callback>
                    <params>
                        <param name="0" xsi:type="boolean">true</param>
                    </params>
                </action>
            </actions>
        </rule>
        ...
    </rules>
    <enabled>true</enabled>
</switcherConfig>

Les 2 règles ci-dessus gèrent à peu près tout en utilisant la configuration XML. Pour des règles plus complexes, vous pouvez également utiliser JavaScript.

Chaque champ dans la forme de composant d'interface utilisateur est un composant qui peut être étendu à l'aide de l' componentattribut pour le <field component="path to your js" ...>...</field>. Vous pouvez ensuite utiliser le champ data.configpour transmettre plus d'informations au composant, au cas où le composant est générique et est réutilisé à plusieurs endroits, combiné avec la propriété importsou exportslien pour transmettre des valeurs à des observables ou à des méthodes.

Pour plus d'informations sur les propriétés de liaison, vous pouvez vérifier les propriétés de liaison des composants d'interface utilisateur

Hungersoft
la source