Author image
Senior Developer

Show elements with form #states when values do not match

I've previously written about dynamic forms in Drupal, using #states to show or hide input boxes depending on other inputs. Since then, Drupal 7 and 8 have both got the ability to combine conditions with OR and XOR operators. This makes it possible to apply changes when a form element's value does not equal something, which is not obvious at all.

It's easy to show something when a value does match. This example shows a text input box for specifying which colour is your favourite when you chose 'Other' from a dropdown list of favourite colours.

$form['other_favorite'] = [
  '#type' => 'textfield',
  '#title' => t('Please specify...'),
  '#states' => [
    'visible' => [   // action to take.
      ':input[name="favorite_color"]' // element to evaluate condition on
        => ['value' => 'Other'],  // condition
    ],
  ],
];

But there's no 'opposite' of the value condition, e.g. to show an element when a text box has anything other than the original value in it. Using the 'XOR' condition (which only passes when some conditions are passed, but not all of them), you can combine the value condition with something that will always pass, in order to simulate it instead. Something like this, which shows a checkbox to confirm a change when you enter anything other than your previous favourite food:

    $form['confirm_change'] = [
      '#type' => 'checkbox',
      '#title' => t('Yes, I really have changed my mind'),
      // Use XOR so that current_pass element only shows if the email
      // address is not the existing value, and is visible. States cannot
      // check for a value not being something, but by using XOR with a
      // condition that should always be true (i.e. the latter one), this
      // will work. Note the extra level of arrays that is required for
      // using the XOR operator.
      '#states' => [
        'visible' => [
          [':input[name="favorite_food"]' => [
            'value' => $original_favorite_food,
          ]],
          'xor',
          [':input[name="favorite_food"]' => [
            // The checked property on a text input box will always come
            // out false.
            'checked' => FALSE,
          ]],
        ],
      ],
    ];

Hopefully the comments in that snippet make sense, but essentially it's about picking a second condition that will always pass, so that the combination only passes when the first condition fails. So in this case, whenever the value does not equal the original value.

By the way, you can do much more than just show or hide form elements, for example, enabling or disabling - see the full list.

Previous article in series: