Question

I have one field select which has some options. One of them will have some fields depend on value, another field will hidden. I have copied and extended component js for my field but it didn't work or I did it wrong way. Ui component supports this feature? How I can achieve this?

Below is what I have done:

<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
        }
    });
});
Was it helpful?

Solution

Try this (Note: Don't forget to replace the line "Namespace" and the line "ModuleName" with your values):

<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>

Where:

  • The child elements visibility is set by default as false;
  • The visibleValue - is field1 value when element should be visible;

Namespace\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/Namespace/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();
        },
    });
});

Result:

Value 0 selected: Value 0 selected

Value 1 selected: Value 1 selected

Value 2 selected: Value 2 selected

Value 3 selected: Value 3 selected

PS: Possibly it not the best solution, but it shall help you

OTHER TIPS

The solution suggested by Magentix will throw an error from time to time when using initialize. It depends on the time it takes for your browser to render the components. In order to fix it you could use setTimeout.

See the code below:

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);
        }
    });
});

This is an old question with multiple answers that work, however I have discovered a solution using what Magento provides (as of 2.1.0) without the need for extending components. As multiple questions have been marked as duplicate and directed here I thought it would be beneficial to provide some information on this option.

All form element ui components that extend Magento_Ui/js/form/element/abstract.js have a switcherConfig setting available for purposes such as hiding/showing elements as well as other actions. The switcher component can be found at Magento_Ui/js/form/switcher for the curious. You can find examples of it being used in sales_rule_form.xml and catalog_rule_form.xml. Of course if you are using your own custom component already you can still use this as long as your component eventually extends abstract which appears to be the case based on the example code provided in the question.

Now for a more specific example to answer the original question.

In Namespace/ModuleName/view/adminhtml/ui_component/your_entity_form.xml you simply need to add the following to the field's settings that does the controlling (i.e. the field that determines which fields are hidden/visible). In your example this would be 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>

Let's break it down a little. The switcher component contains an array of rules which is what we're building out here. Each <rule> has a name which is a number in this example. This name is the array key/index for this item. We're using numbers as array indexes. Strings should work too but I haven't tested this theory. UPDATE - As mentioned by @ChristopheFerreboeuf in the comments, strings to not work here. These are arrays and should start with 0, not strings or 1.

Inside each rule we pass two arguments.

  1. value - This is the value of field1 which should trigger the actions defined below.
  2. actions - Here we have another array. These are the actions to be triggered when this rule's conditions are met. Again, each action's name is just the array index/key of that item.

Now each action has two arguments as well (with an optional 3rd).

  1. target - This is the element you wish to manipulate under this action. If you aren't familiar with how ui_component element names are composed in Magento you can check out Alan Storm's article. It's basically something like {component_name}.{component_name}.{fieldset_name}.{field_name} in this example.
  2. callback - Here is the action to be taken on the above mentioned target. This callback should be a function that is available on the element targeted. Our example uses hide and show. This is where you can start to expand on the functionality available. The catalog_rule_form.xml example I mentioned earlier uses setValidation if you wish to see a different example.
  3. You can also add <params> to any action that calls for them. You can see this in the catalog_rule_form.xml example as well.

Finally the last item inside switcherConfig is <enabled>true</enabled>. This should be pretty straight forward, it's a Boolean to enable/disable the switcher functionality we just implemented.

And we're done. So using the example above what you should see is field field2Depend1 displayed if you choose an option with value 2 on field1, and field3Depend1 displayed if you choose an option with value 3.

I have tested this example using only hide and show on a required field and it does appear to take visibility into account for validation. In other words, if field2Depend1 is required it will only be required when visible. No need for further configuration for that to work.

Hope this provides some help for anyone looking for a more out-of-the-box solution.

There's a lot of answers for this question, but most of them make either make assumptions on whether the uiRegistry is fully loaded, or use setTimeout to clear the call stack, and wait for the next eventloop (which in my opinion is still the wrong way to do it - since you can't be sure when the other ui components have loaded - correct me if I'm wrong).

Firstly, of course, add your custom JS component to the field config (see other answers for details):

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

Then, here's the custom UI component that hides or shows the dependent fields - with comments to explain what's happening.

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);
        }
    });
});

If you have got error like Field is Undefined when initialized field visibility, Use setTimeout() to load that depended fields:

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();
},

Custom component with 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;
        }
    });
});

There are a few ways for handling field dependencies, for simple Yes/No dropdown, a checkbox or a switcher, you can use the imports or exports linking properties in Magento 2. The solution is discussed in detail here: Dependent fields in UI component forms in Magento 2 without Javascript for boolean fields:

<!-- 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>

To handle other type of values such as dependency on a list of values in a dropdown or although unlikely, a value of an input field, you can use the switcherConfig. Check Dependent fields in ui-component forms in Magento 2 without Javascript for information.

<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>

The above 2 rules, handle pretty much everything using XML config. For more complex rules, you can use JavaScript as well.

Each field in the UI component form is a component which can be extended using the component attribute for the <field component="path to your js" ...>...</field>. You can then use the field data.config to pass more information to the component, in case the component is generic and is re-used in multiple places, combined with the imports or exports linking property to pass values to observables or methods.

For more information on the linking properties you can check Linking properties of UI components

Just in case someone struggles with the Erfan solution, you have to pass the full path to the fields in dependentFieldNames, e.g.:

       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'
    ],

I'm not sure why form_name has to be 2 times, but this has worked for me.

To debug this I put console.log(query); in static/adminhtml/Magento/backend/en_US/Magento_Ui/js/lib/registry/registry.js 223rd line ( the get() function just before this._addRequest(query, callback) )

Licensed under: CC-BY-SA with attribution
Not affiliated with magento.stackexchange
scroll top