DEV Community

Joel Rainwater
Joel Rainwater

Posted on

Using field dependency in Magento 2

Skip the details, show me the code

Quick Summary

One of the most common questions I see and have asked myself for Magento 2 is how to create fields that are dynamically visible, dependent on the value of another field. This article will walk through how to do this using the tools already provided by Magento.

I have seen a lot of solutions out there, most of which involve building this feature yourself with some custom Javascript, which is just fine however I prefer to use the tools already provided by the system when available. This article is an extension of my answer on the Magento Stack Exchange here -

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…

Behind the Curtain

The steps in this article walk through how to implement this functionality. But to give you a high-level view at what functionality we will be taking advantage of, and where you can find it used in the wild (i.e. core Magento), here's some quick info.
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.

Requirements

This article was written using Magento 2.3, however this feature has been available as of Magento 2.1.0.
This article assumes you have a custom module you are implementing this functionality into. If you need help building a module, check out the Magento dev docs.

Set the Stage

To make things easier, let's define some requirements for a feature we're going to build. I am going to use an example of something I built recently so that I can give real examples.

Let's say you have created a new entity in Magento called a Shipping Carrier, and you have created a view in admin to edit these Shipping Carriers. To use the feature we are discussing in this article, you should be using Magento's UI Components. More on that later.

While editing a Shipping Carrier, we want the ability to enable Tracking for some carriers, and if Tracking is enabled we want to provide a Tracking URL. We will create two fields for this:

  1. Tracking Enabled (tracking_enabled)
  2. Tracking URL (tracking_url)

To make things cleaner for the content editors, we will hide the Tracking URL field unless Tracking Enabled is set to Enabled (yes this is redundant, but naming is the hardest part of being a developer, right?). Below is a screen recording of the end result we will be trying to accomplish.

Editing a Shipping Carrier in Magento 2. There is a field titled

For the purposes of this article, I will skip the creation of a module and the creation of the Shipping Carrier controller and UI Component form, as this is focused on toggling visibility of fields in a UI Component form. You can find a number of articles showing how to create UI Component forms in Magento 2.

What to do

Finally, let's get started on the code. Once you have created your module and the appropriate controller and layout, you should have a file in your module like {Namespace}/{ModuleName}/view/adminhtml/ui_component/shipping_carrier_form.xml. This file will change depending on what type of entity you are working on, and the name of the UI Component form you created. For the Product Edit form for example, it would be product_form.xml.

First, let's add our two new fields to the shipping_carrier_form UI Component form. The relevant part of the file will look something like this.



<?xml version="1.0" encoding="UTF-8"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
  ...
  <fieldset name="carrier_information" sortOrder="10">
    ...
    <field name="tracking_enabled" formElement="select">
      <argument name="data" xsi:type="array">
        ...
      </argument>
      <settings>
        <validation>
          <rule name="required-entry" xsi:type="boolean">true</rule>
        </validation>
        <dataType>number</dataType>
        <label translate="true">Tracking Enabled</label>
        <scopeLabel>[WEBSITE]</scopeLabel>
        <visible>true</visible>
        <dataScope>tracking_enabled</dataScope>
      </settings>
      <formElements>
        <select>
          <settings>
            <options>
              <option name="0" xsi:type="array">
                <item name="value" xsi:type="number">1</item>
                <item name="label" xsi:type="string" translate="true">Enabled</item>
              </option>
              <option name="1" xsi:type="array">
                <item name="value" xsi:type="number">0</item>
                <item name="label" xsi:type="string" translate="true">Disabled</item>
              </option>
            </options>
          </settings>
        </select>
      </formElements>
    </field>
    <field name="tracking_url" formElement="input">
      <argument name="data" xsi:type="array">
        ...
      </argument>
      <settings>
        <validation>
          <rule name="required-entry" xsi:type="boolean">true</rule>
        </validation>
        <dataType>text</dataType>
        <label translate="true">Tracking URL</label>
        <scopeLabel>[WEBSITE]</scopeLabel>
        <visible>true</visible>
        <dataScope>tracking_url</dataScope>
      </settings>
    </field>
  </fieldset>
</form>



Enter fullscreen mode Exit fullscreen mode

So now we have the two fields always visible and always required for Shipping Carriers. To see the changes you may need to clear cache if you have it enabled.

Now, let's get to the problem at hand. We want to hide the Tracking URL field unless Tracking is Enabled. At this point, we need to add a bit of XML into the controller field. By controller field, I mean the field that controls the visibility of the other field(s). In our case, this will be the field tracking_enabled. We need to add <switcherConfig> inside the <settings> node of this field. It should look something like this.



<switcherConfig>
  <rules>
    <rule name="0">
      <value>0</value>
      <actions>
        <action name="0">
          <target>shipping_carrier_form.shipping_carrier_form.carrier_information.tracking_url</target>
          <callback>hide</callback>
        </action>
      </actions>
    </rule>
    <rule name="1">
      <value>1</value>
      <actions>
        <action name="0">
          <target>shipping_carrier_form.shipping_carrier_form.carrier_information.tracking_url</target>
          <callback>show</callback>
        </action>
      </actions>
    </rule>
  </rules>
  <enabled>true</enabled>
</switcherConfig>


Enter fullscreen mode Exit fullscreen mode

Now the Tracking URL field will be hidden unless the Tracking Enabled field is set to Enabled. It will also only be required if it is visible. But let's break down what we just added so we can understand what it does.

The <switcherConfig> 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 index for this item. As these are arrays, they should start with 0, not strings or 1.

Inside each <rule> we pass two arguments.

  1. <value> - This is the value of tracking_enabled 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 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 JavaScript 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! The final XML for our new fields should look like this.



<field name="tracking_enabled" formElement="select">
  <argument name="data" xsi:type="array">
    ...
  </argument>
  <settings>
    <validation>
      <rule name="required-entry" xsi:type="boolean">true</rule>
    </validation>
    <dataType>number</dataType>
    <label translate="true">Tracking Enabled</label>
    <scopeLabel>[WEBSITE]</scopeLabel>
    <visible>true</visible>
    <dataScope>tracking_enabled</dataScope>
    <switcherConfig>
      <rules>
        <rule name="0">
          <value>0</value>
          <actions>
            <action name="0">
              <target>shipping_carrier_form.shipping_carrier_form.carrier_information.tracking_url</target>
              <callback>hide</callback>
            </action>
          </actions>
        </rule>
        <rule name="1">
          <value>1</value>
          <actions>
            <action name="0">
              <target>shipping_carrier_form.shipping_carrier_form.carrier_information.tracking_url</target>
              <callback>show</callback>
            </action>
          </actions>
        </rule>
      </rules>
      <enabled>true</enabled>
    </switcherConfig>
  </settings>
  <formElements>
    <select>
      <settings>
        <options>
          <option name="0" xsi:type="array">
            <item name="value" xsi:type="number">1</item>
            <item name="label" xsi:type="string" translate="true">Enabled</item>
          </option>
          <option name="1" xsi:type="array">
            <item name="value" xsi:type="number">0</item>
            <item name="label" xsi:type="string" translate="true">Disabled</item>
          </option>
        </options>
      </settings>
    </select>
  </formElements>
</field>
<field name="tracking_url" formElement="input">
  <argument name="data" xsi:type="array">
    ...
  </argument>
  <settings>
    <validation>
      <rule name="required-entry" xsi:type="boolean">true</rule>
    </validation>
    <dataType>text</dataType>
    <label translate="true">Tracking URL</label>
    <scopeLabel>[WEBSITE]</scopeLabel>
    <visible>true</visible>
    <dataScope>tracking_url</dataScope>
  </settings>
</field>


Enter fullscreen mode Exit fullscreen mode

Good luck with your dynamic fields! Feel free to post comments if this article helped you build something, or if you have any questions or other feedback.

Top comments (1)

Collapse
 
rui_martins_73183c237873a profile image
Rui Martins • Edited

Hi, I do it but don't work to me: can you help me?
<?xml version="1.0" ?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
<argument name="data" xsi:type="array">
<item name="js_config" xsi:type="array">
<item name="provider" xsi:type="string">
toogas_attributesetcustomoptions_attributesetcustomoptions_form.attributesetcustomoptions_form_data_ </item>
</item>
<item name="label" xsi:type="string" translate="true">General Information</item>
</argument>
<settings>
<buttons>
<button name="back"
class="Toogas\AttributeSetCustomOptions\Block\Adminhtml\AttributeSetCustomOptions\Edit\BackButto <button name="delete"
class="Toogas\AttributeSetCustomOptions\Block\Adminhtml\AttributeSetCustomOptions\Edit\DeleteBut <button name="save"
class="Toogas\AttributeSetCustomOptions\Block\Adminhtml\AttributeSetCustomOptions\Edit\SaveButto <button name="save_and_continue"
class="Toogas\AttributeSetCustomOptions\Block\Adminhtml\AttributeSetCustomOptions\Edit\SaveAndC </buttons>
<layout>
<navContainerName>left</navContainerName>
<type>tabs</type>
</layout>
<namespace>toogas_attributesetcustomoptions_attributesetcustomoptions_form</namespace>
<dataScope>data</dataScope>
<deps>
<dep>
toogas_attributesetcustomoptions_attributesetcustomoptions_form.attributesetcustomoptions_form_data_ </dep>
</deps>
</settings>
<dataSource name="attributesetcustomoptions_form_data_source">
<argument name="data" xsi:type="array">
<item name="js_config" xsi:type="array">
<item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
</item>
</argument>
<settings>
<submitUrl path="*/*/save"/>
</settings>
<dataProvider name="attributesetcustomoptions_form_data_source"
class="Toogas\AttributeSetCustomOptions\Model\AttributeSetCustomOptions\DataProvider">
<settings>
<requestFieldName>attributesetcustomoptions_id</requestFieldName>
<primaryFieldName>attributesetcustomoptions_id</primaryFieldName>
</settings>
</dataProvider>
</dataSource>
<fieldset name="general">
<settings>
<label translate="true">General</label>
</settings>
<field name="option_title" sortOrder="10" formElement="input">
<settings>
<dataType>text</dataType>
<label translate="true">Option Title</label>
<dataScope>option_title</dataScope>
<validation>
<rule name="required-entry" xsi:type="boolean">true</rule>
</validation>
</settings>
</field>
<field name="attribute_set_id" formElement="select" sortOrder="10">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="source" xsi:type="string">AttributeSetCustomOptions</item>
</item>
</argument>
<settings>
<dataType>text</dataType>
<label translate="true">Attribute Set</label>
<dataScope>attribute_set_id</dataScope>
<validation>
<rule name="required-entry" xsi:type="boolean">false</rule>
</validation>
</settings>
<formElements>
<select>
<settings>
<options class="Toogas\AttributeSetCustomOptions\Model\Source\AttributeSet"/>
</settings>
</select>
</formElements>
</field>
<field name="option_type" sortOrder="20" formElement="select">
<argument name="data" xsi:type="array">
<item name="options" xsi:type="object">Toogas\AttributeSetCustomOptions\Model\Source\OptionType</it <item name="config" xsi:type="array">
<item name="dataType" xsi:type="string">text</item>
<item name="label" xsi:type="string" translate="true">Option Type</item>
<item name="formElement" xsi:type="string">select</item>
<item name="source" xsi:type="string">AttributeSetCustomOptions</item>
<item name="dataScope" xsi:type="string">option_type</item>
<item name="default" xsi:type="string">0</item>
</item>
</argument>
<settings>
<switcherConfig>
<rules>
<rule name="0">
<value>0</value>
<actions>
<action name="0">
<target>toogas_attributesetcustomoptions_attributesetcustomoptions_form.toogas_attributeseax_characters</target>
<callback>hide</callback>
</action>
</actions>
</rule>
<rule name="1">
<value>1</value>
<actions>
<action name="0">
<target>toogas_attributesetcustomoptions_attributesetcustomoptions_form.toogas_attributeseax_characters</target>
<callback>show</callback>
</action>
</actions>
</rule>
</rules>
<enabled>true</enabled>
</switcherConfig>
</settings>
</field>
<field name="is_required" sortOrder="30" formElement="checkbox">
<settings>
<dataType>boolean</dataType>
<label translate="true">Is Required</label>
<dataScope>is_required</dataScope>
</settings>
<formElements>
<checkbox>
<settings>
<prefer>toggle</prefer>
<valueMap>
<map name="false" xsi:type="number">0</map>
<map name="true" xsi:type="number">1</map>
</valueMap>
</settings>
</checkbox>
</formElements>
</field>
<field name="max_characters" sortOrder="40" formElement="input">
<settings>
<dataType>text</dataType>
<label translate="true">Max Characters</label>
<dataScope>max_characters</dataScope>
</settings>
</field>
</fieldset>
<fieldset name="options">
<settings>
<label translate="true">Options</label>
</settings>
<dynamicRows name="sub_options">
<settings>
<label translate="true">Options</label>
<addButtonLabel translate="true">Add Option</addButtonLabel>
<additionalClasses>
<class name="admin__field-wide">false</class>
<class name="admin__field">true</class>
</additionalClasses>
<componentType>dynamicRows</componentType>
</settings>
<container name="record" component="Magento_Ui/js/dynamic-rows/record">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="isTemplate" xsi:type="boolean">true</item>
<item name="is_collection" xsi:type="boolean">true</item>
<item name="componentType" xsi:type="string">container</item>
</item>
</argument>
<field name="name" sortOrder="10" formElement="input">
<settings>
<dataType>text</dataType>
<label translate="true">Name</label>
<dataScope>name</dataScope>
<validation>
<rule name="required-entry" xsi:type="boolean">true</rule>
</validation>
</settings>
</field>
<field name="img" sortOrder="20" formElement="fileUploader">
<settings>
<label translate="true">Image</label>
<componentType>fileUploader</componentType>
</settings>
<formElements>
<fileUploader>
<settings>
<uploaderConfig>
<param xsi:type="string" name="url">
toogas_attributesetcustomoptions/uploader/image
</param>
</uploaderConfig>
<previewTmpl>Toogas_AttributeSetCustomOptions/image-preview</previewTmpl>
<allowedExtensions>jpg jpeg gif png svg</allowedExtensions>
<maxFileSize>4194304</maxFileSize>
<isMultipleFiles>false</isMultipleFiles>
<placeholderType>img</placeholderType>
</settings>
</fileUploader>
</formElements>
</field>
<field name="price" sortOrder="50" formElement="input">
<settings>
<dataType>text</dataType>
<label translate="true">Price</label>
<dataScope>price</dataScope>
</settings>
</field>
<actionDelete template="Magento_Backend/dynamic-rows/cells/action-delete">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="fit" xsi:type="boolean">true</item>
</item>
</argument>
<settings>
<additionalClasses>
<class name="some-class">true</class>
</additionalClasses>
<dataType>text</dataType>
<label>Delete</label>
<componentType>actionDelete</componentType>
</settings>
</actionDelete>
</container>
</dynamicRows>
</fieldset>
</form>