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

An alternative module to conditional fields is the Field conditional states module.

Behind the scenes it uses the form API states you are discussing here, whereas the conditional fields module uses a custom solution.

Currently it doesn't support all field types but it is relatively easy to add support for new field types and there are currently a number of patches in the queue to add new fields that should be committed pretty soon.

It seems as though field conditional states is more actively maintained.

I've been looking for a solution to OR'ing form states for a while and this is the only documentation i've found on the subject; Really appreciate you putting this out there :)

Cheers,
-Brad

My specific problem is getting a step driven form using #states. My implementation uses a hidden numeric step field which is incremented as you advance. So you would make a fieldset visible when step==2 and make it invisible when step!=2. There is a Next and a Previous HTML buttons with Javascript onClick methods that increment the step field. I thought I read that there was a NOT (!) syntax, but cannot find an example of it. Thank you for your article.

Really appreciate these examples. I'd been wondering how to implement OR logic for awhile.

To note, this appears to work the same way in 8.x.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <cpp>, <java>, <php>. The supported tag styles are: <foo>, [foo].
  • Web page addresses and email addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.

About the Author

Slavko Pesic, Development Team Lead

Slavko is a Web Programmer at Metal Toad Media and a Computer and Information Science graduate from University of Oregon. Introduced to Commodore 64 at the age of 5 he found his passion for technology and is eager to learn about anything tech related. Tinkering and solving puzzles is second nature to him.

In his spare time, Slavko can be found skydiving, camping, hiking, backpacking or playing disc golf and ultimate frisbee with friends. When in his natural setting (in front of a computer), he will spend hours reading articles on sciences and technology as well as playing video games.