Drupal

Drupal 7 Form API: Using #states with multiple conditionals (AND, OR and XOR)

I've been playing with D7 forms lately and have found #states to be somewhat challenging due to lack of documentation on Form API page. I've poked around a bit and decided to write a blog with my findings in case someone else is in need of this info down the road. If you are looking for a robust solution for conditional fields, I would suggest looking into


Filed under:

This article is one of Metal Toad's Top 20 Drupal Tips. Enjoy!

I've been playing with D7 forms lately and have found #states to be somewhat challenging due to lack of documentation on Form API page.
I've poked around a bit and decided to write a blog with my findings in case someone else is in need of this info down the road.
If you are looking for a robust solution for conditional fields, I would suggest looking into conditional fields or a more lightweight approach field conditional states (thanks for the comment Arjan and rooby).

If you have a simple, one off case where you have a conditional field and are reluctant to introduce 2-3k lines of code, you can use hook_form_alter() and #states API.
For this example I will use a very simple content type, Item, with following fields:

Title (title) [text]
Item Type (field_item_type) [select_list]:

    tv_show|TV Show
    movie|Movie
    banana|Banana for Scale
    truck|Truck

Format (field_format) [select_list]:

    video_dvd|DVD
    video_bluray|Blu-Ray
    video_3d|3D Blu-Ray

So I want to hide format field by default and display it only in case we are creating an Item of type TV Show or Movie.
I've created a simple module with the form alter that looks like this:

<?php
 
/**
 *  Implements hook_form_alter().
 */
function mtm_formapi_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'item_node_form') {
    $form['field_format']['#states'] = array(
      'visible' => array(
        ':input[name="field_item_type[und]"]' => array('value' => 'tv_show'),
      ),
    );
  }
}

The above code is pretty much it when it comes to Form API documentation on #states.
What it does is display field_format if the item type is TV Show, that is if :input[name="field_item_type[und]"] selector matches tv_show, otherwise it hides the format field.
But what about if there is more than one valid condition or if we need to meet multiple conditions?
Unfortunately, this is where things get a bit chaotic. There is not a common pattern that you can follow. Instead, you will need to change your strategy a bit depending on what your need is.
In case where you have multiple conditions which all need to be met, you can follow the pattern established in #state API docs:

    '[field-selector]' => array('[evaluate]'),
    '[field-selector2]' => array('[evaluate]'),
    ...

<?php
 
/**
 *  Implements hook_form_alter().
 */
function mtm_formapi_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'item_node_form') {
    $form['field_format']['#states'] = array(
      'visible' => array(
        ':input[name="field_item_type[und]"]' => array('value' => 'tv_show'),
        '#edit-title' => array('value' => 'test'),
      ),
    );
  }
}

The above will only show format field if title is test AND Item type is TV Show.
However, our original task was to display format if our Item type is TV Show OR Movie. The pattern in the docs will not work in this case.
Instead the pattern is the following:

    array('[field-selector]' => array('[evaluate]')),
    '[OR/XOR]',
    array('[field-selector2]' => array('[evaluate]')),
    ...

<?php
 
/**
 *  Implements hook_form_alter().
 */
function mtm_formapi_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'item_node_form') {
    $form['field_format']['#states'] = array(
      'visible' => array(
        array(
          array(':input[name="field_item_type[und]"]' => array('value' => 'tv_show')),
          'or',
          array(':input[name="field_item_type[und]"]' => array('value' => 'movie')),
        ),
      ),
    );
  }
}

Note above, we are using indexed instead of associative array and each individual statement is wrapped in an array.
The same pattern will work for XOR statements, all you have to do is replace 'or' with 'xor'. In addition, OR condition is implied when using the pattern above. That is, if I were to omit 'or' in the previous statement, the code would still behave as expected.

The support for OR and XOR came a bit latter, thus the chaos in the pattern and documentation.
One thing to keep in mind, if you are using jquery_update 1.7 and are using the OR and XOR, make sure you update it to the latest version as it overrides the form states.js and 1.7 doesn't support OR/XOR's.

Similar posts

Get notified on new marketing insights

Be the first to know about new B2B SaaS Marketing insights to build or refine your marketing function with the tools and knowledge of today’s industry.