function drupal_process_states

You are here

7 common.inc drupal_process_states(&$elements)
8 common.inc drupal_process_states(&$elements)

Adds JavaScript to change the state of an element based on another element.

A "state" means a certain property on a DOM element, such as "visible" or "checked". A state can be applied to an element, depending on the state of another element on the page. In general, states depend on HTML attributes and DOM element properties, which change due to user interaction.

Since states are driven by JavaScript only, it is important to understand that all states are applied on presentation only, none of the states force any server-side logic, and that they will not be applied for site visitors without JavaScript support. All modules implementing states have to make sure that the intended logic also works without JavaScript being enabled.

#states is an associative array in the form of:

array(
  STATE1 => CONDITIONS_ARRAY1,
  STATE2 => CONDITIONS_ARRAY2,
  ...
)

Each key is the name of a state to apply to the element, such as 'visible'. Each value is a list of conditions that denote when the state should be applied.

Multiple different states may be specified to act on complex conditions:

array(
  'visible' => CONDITIONS,
  'checked' => OTHER_CONDITIONS,
)

Every condition is a key/value pair, whose key is a jQuery selector that denotes another element on the page, and whose value is an array of conditions, which must bet met on that element:

array(
  'visible' => array(
    JQUERY_SELECTOR => REMOTE_CONDITIONS,
    JQUERY_SELECTOR => REMOTE_CONDITIONS,
    ...
  ),
)

All conditions must be met for the state to be applied.

Each remote condition is a key/value pair specifying conditions on the other element that need to be met to apply the state to the element:

array(
  'visible' => array(
    ':input[name="remote_checkbox"]' => array('checked' => TRUE),
  ),
)

For example, to show a textfield only when a checkbox is checked:

$form['toggle_me'] = array(
  '#type' => 'checkbox',
  '#title' => t('Tick this box to type'),
);
$form['settings'] = array(
  '#type' => 'textfield',
  '#states' => array(
    // Only show this field when the 'toggle_me' checkbox is enabled.
    'visible' => array(
      ':input[name="toggle_me"]' => array('checked' => TRUE),
    ),
  ),
);

The following states may be applied to an element:

  • enabled
  • disabled
  • required
  • optional
  • visible
  • invisible
  • checked
  • unchecked
  • expanded
  • collapsed

The following states may be used in remote conditions:

  • empty
  • filled
  • checked
  • unchecked
  • expanded
  • collapsed
  • value

The following states exist for both elements and remote conditions, but are not fully implemented and may not change anything on the element:

  • relevant
  • irrelevant
  • valid
  • invalid
  • touched
  • untouched
  • readwrite
  • readonly

When referencing select lists and radio buttons in remote conditions, a 'value' condition must be used:

  '#states' => array(
    // Show the settings if 'bar' has been selected for 'foo'.
    'visible' => array(
      ':input[name="foo"]' => array('value' => 'bar'),
    ),
  ),

Parameters

$elements: A renderable array element having a #states property as described above.

See also

form_example_states_form()

1 call to drupal_process_states()
drupal_render in includes/common.inc
Renders HTML given a structured array tree.

File

includes/common.inc, line 4659
Common functions that many Drupal modules will need to reference.

Code

function drupal_process_states(&$elements) {
  $elements['#attached']['library'][] = array('system', 'drupal.states');
  $elements['#attached']['js'][] = array(
    'type' => 'setting',
    'data' => array('states' => array('#' . $elements['#id'] => $elements['#states'])),
  );
}

Comments

Two gotchas that may save others some time:

1. Fields need to have the language in the selector or the name doesn't match
2. When comparing against a select's value that is a number, you need to cast the value to a string or it won't match (see http://drupal.org/node/879580)

An example of both:

    if (isset($form['field_tohide'])) {
      // Get the name of the remote field with language
      $lang = $form['field_term_location']['#language'];
      $fhsname = 'field_term_location['. $lang .']';
      // Set up the state rule so that we hide this field when the value is "3"
      $form['field_tohide']['#states'] = array(
        'invisible' => array(
          ':input[name="'. $fhsname .'"]' => array('value' => (string) '3'),
        )
      );
    }

The above example is in the context of a hook_form_node_form_alter() function that hides one field (field_tohide for illustration purposes) when a certain term is selected in another field (in this example, a taxonomy select called field_term_location). First we need to get the language in the remove field (turning :input[name="field_term_location"] into :input[name="field_term_location[und]"]). The next thing to notice is that the value we are comparing to is "(string) '3'".

Thanks for the comment, very helpful

Not able to get a radio button option if checked to show and if un-checked to hide another form element. It will show if checked just fine but if another radio button option is selected in the group, the target element does not hide.

  $form['field_request_subscribers']['#states'] = array(
    'visible' => array('#edit-field-request-audience-und-selective' => array('checked' => TRUE),
    ),
  );

Tried adding state behaviors on one of the other two radio button options to make the target element invisible if selected but that did nothing.

  $form['field_request_subscribers']['#states'] = array(
    'visible' => array('#edit-field-request-audience-und-selective' => array('checked' => TRUE),
    ),
    'invisible' => array('#edit-field-request-audience-und-all' => array('checked' => TRUE),
    ),
  );

Also tried

    'visible' => array('#edit-field-request-audience-und-selective' => array('checked' => TRUE),
    ),
    'invisible' => array('#edit-field-request-audience-und-selective' => array('checked' => FALSE),
    ),

If the element is a checkbox vs a radio group it works just fine.

Any suggestions?

If you are working with radio buttons or select lists you can't use 'checked' => 'true/false'.

Instead, you must use 'value' => 'name', where 'name' is the name of the radio button that you require to be selected before making your field visible/invisible.

I often forget this myself but it is mentioned at the top of the page.

When referencing select lists and radio buttons in remote conditions, a 'value' condition must be used:

<?php
 
'#states' => array(
   
// Show the settings if 'bar' has been selected for 'foo'.
   
'visible' => array(
     
':input[name="foo"]' => array('value' => 'bar'),
    ),
  ),
?>

where 'name' is the name of the radio button that you require to be selected before making your field visible/invisible.

There is a lack of documentation for the more general use case of making another field visible if/once ANY radio is selected. This is widely used UI, but it's not clear how to implement it in Drupal #states.

True enough. Meanwhile, I found http://drupal.org/node/767268#comment-2881864 to be of help when I was in the same situation.

Hi,

I done show hide form and fields by using the state and jquery script.
At Form side I added the state condition.

$form['credit_card']['code'] = array(
'#type' => 'textfield',
'#title' => !empty($fields['code']) ? $fields['code'] : t('CVV Number'),
'#states' => array(
'required' => array(
':input[name="field_payment[und]"]' => array('value' => 'authnet_aim|commerce_payment_authnet_aim'),
),
'visible' => array(
':input[name="field_payment[und]"]' => array('value' => 'authnet_aim|commerce_payment_authnet_aim'),

),
),
'#default_value' => $default['code'],
'#attributes' => array('autocomplete' => 'off'),
'#maxlength' => 4,
'#size' => 4,

);

At jquery side , I added a new JavaScript file .
1. Call js file at your page where you want to execute , in my case it was at node add case so I added a hook_node_alter and
$modulePath = drupal_get_path('module', 'mymodule');
drupal_add_js($modulePath . '/mycustom.js');

2. in mycustom.js, I wrote the follwing code.

Drupal.behaviors.mymodule = {
attach : function()
{

$("input[name=field_payment[und]]:radio").bind( "change", function(event, ui) {

if($(this).val() == "authnet_aim|commerce_payment_authnet_aim")
{
$("#new_creaditCard").show();

}else
{ $("#new_creaditCard").hide();}
});

}

}

And get the things working , but for this I need to make my credit card option selected by default.

Elements of type 'markup' don't respond to the 'visible' property.

See this blog post for more details and a workaround: http://www.bywombats.com/blog/06-25-2011/using-containers-states-enabled...

The following states exist for both elements and remote conditions, but are not fully implemented and may not change anything on the element

Does "may not" here mean, "might not" or does it mean "must not"?

In other words, if someone tries to use these states, is it expected that nothing will happen or is it expected that something might happen?

Another great blog post about the states system: http://randyfay.com/states

I'm trying to make a field optional upon condition of a radio button selection, like so:

$form['body']['#states'] = array(
  'optional' => array(
    ':input[name="hero_type_selection"]'
    => array('value' => 'public_figure'),
  ),
);

The problem is, any time I select a radio button that is NOT "public_figure," the required tag is added back to the body field, even if it's already there. The result is something like this:

Body (Edit summary)*****

Is there any way to get it to stop adding more and more asterisks?

EDIT: It turned out I simply had an extraneous Drupal.attachBehaviors() in my jQuery include, so this is working fine as-is!

I was having trouble finding a way of getting a form field (the cardinality field on a field edit form) hidden if a select widget had any one of a number of values, essentially I wanted to specify the element should be invisible if value IN (x,y,z). Ideally we'd need OR operations implemented for values to do this, but #states are always an AND list of conditions (as stated above) ... The inverse (visible), has the same issue - I'd want to specify visible if value IN (a,b,c), and if there's no NOT operator for values, then I couldn't do visible if value != a AND value != b AND value != c.

However, as it happens, It appears to be the case that you can use "!value" to denote value != (not mentioned above). So that gets us almost able to use an OR lists equivalent rewritten as an AND ... except for the fact that you can still only specify one value for each key - a repeated JQUERY_SELECTOR will overwrite a preceding one, as the array is keyed off the JQUERY_SELECTOR.

But ... for the sake of getting the array key unique, you can add some random unique nonsense to the end of it so long as it's syntactically correct and wouldn't actually match anything, so I ended up with something like this to implement invisible if value IN (x,y,z) - assume here the full set of possible values is (a,b,c,x,y,z):

<?php
    $all
= array('a', 'b', 'c', 'x', 'y', 'z');
   
$visible = array('a', 'b', 'c');
    foreach (
array_diff($all, $visible) as $i => $vocab) {
     
$form['foo']['#states']['visible'][':input[name="bar"], my-nonsense-dummy-element' . $i] = array('!value' => $vocab);
    }
?>

Hopefully this may be of use to someone, or might inspire someone to point out a better way of doing this... Or maybe it's time to make it possible to specify OR lists for values.

I fought with #states a bit as I only wanted to show an element if a group of radios was not yet selected, since 'empty' did not seem to work on a group of radios. However, this did:

    '#states' => array(
      'invisible' => array(
        ':input[name="trial_day"]' => array('!value' => false),
      ),
    ),

Note: Another gotcha. 'false' must be in lowercase, or it won't work in IE.

This may also work with selects, but I haven't tried.

HI! I have used #states to display an attribute text field if a corresponding in the add to cart works fine in node full view but when viewing multiple nodes in single page with a teaser view ....it doesn't work why?

Note that "OR" lists were just committed to D7 and will be available in the next release (7.13): http://drupal.org/node/735528

Something else that worked for me with radio buttons:

if the options for field_one are 1, 2, 3, 4 and field_two should be visible if option 3 or 4 is selected the code looks like this:

<?php
  $form
['field_two']['#states'] = array(
   
'invisible' => array(
     
':input[name="field_one[und]"],value="3"' => array('!value' => (string)'3'),
     
':input[name="field_one[und]"],value="4"' => array('!value' => (string)'4'),
    ),
  );
?>

The html code for reference:

<input type="radio" id="edit-field-one-und-1" name="field_one[und]" value="1" class="form-radio" />

The proper way to do OR is:

<?php
$form
['field_two']['#states'] = array(
   
'visible' => array(
     
':input[name="field_one[und]"]' => array(
        array(
'value' => '3'),
        array(
'value' => '4'),
      ),
    ),
  );
?>

When using "checkboxes", ie, multiple-checkboxes in a single form-element, you can reference them as follows:

<?php
$states
= array(
 
'visible' => array(
   
':input[name="process[highwire_figures]"]' => array('checked' => TRUE),
   ),
);
?>

Note that in this example "process" in the form-element we are targeting, and "highwire_figures" is the option key.

I just found an issue in the javascript for states - there is some code to check the type of a variable that doesn't work in IE. The code is supposed to find the type of the variable and choose which special comparison function might be needed. Since the code doesn't work in IE the special comparison functions are never used.

So if you declare the value as an integer it will fail in IE e.g. the following code fails (SEGMENT_TID_BDV is a defined integer constant for a term id):

  $form['field_bdv_courses']['#states'] = array(
    'visible' => array(
      ':input[name^="field_segment_single"]' => array('value' => SEGMENT_TID_BDV),
  ));

Luckily I realised that there's a very simple work around - convert the integer values into string values!

So I changed the SEGMENT_TID_BDV constant to be a string and everything now works in IE.

So the moral of the story is for IE support never declare states conditional values as integers even if the field's is integer storing integers.

thank you. very interesting point here.

Thanks, you're a life saver!

I'm not sure exactly where this really ought to be filed, but, a thought after having worked with states for a bit -- it might be nice to be able to fire a hunk of javascript when a state action happens. I was just working on some code where I had clicks on radio buttons activating other form elements, and wanted to tweak the css of those elements in response to the clicks (e.g., setting a lower opacity on the disabled items). I was able to do this with some jquery, but it seems like it should be possible, and helpful, to integrate this more directly into the states system. Just a thought...

I can never find this:
http://drupal.org/node/1464758

hello there,

i can't get it work on a date field with disabled state.

<?php
  $form
['foo'] = array(
   
'#type' => 'textfield',
   
'#states' => array(
     
'disabled' => array(
       
':input[name="bar"]' => array('value' => 0),
      ),
    ),
  );
?>

when bar gets triggered nothing happens if foo is date when it works if foo is textfield. any idea?

thanks!

It's handy sometimes to not be so specific on the fieldname in case there could be a variable prefix to the fieldname like if it's in a fieldset.

Using
'visible' => array(
':input[name="field_subsribe_newsletter"]' => array('checked' => FALSE),
),

Won't match if you nest that field now in a fieldset - instead use:
'visible' => array(
':input[name*="field_anonymous_donation"]' => array('checked' => FALSE),
),

using [name* ... wildcard was very helpful.

I would like to show a comment field if the value of a temperature field is below 3 or above 8, so if the users enter for example "2.4" or "9.2" the comment field should be shown, whereas "4.3" shouldn't, because it's within the range of 3 to 8 degrees.

Does someone has an exemple of using States API to work within a field collection in a node form ?

I tried via hook_form_alter to do the trick but doesn't seem to work

I created a field_sub_article field_collection field with a select field_sub_article_type field that should make invisible field field_sub_article_title when the value of my select list body_only is selected.

<?php
 
elseif ($form_id == 'article_node_form') {
   
$sub_article_lang = $form['field_sub_article']['#language'];
    foreach (
$form['field_sub_article'][$sub_article_lang] as $key => $form_sub) {
      if (
is_numeric($key)) {
       
$sub_article_type_lang = $form_sub['field_sub_article_type']['#language'];
       
$input = ':input[name="field_sub_article[' . $sub_article_lang . '][' . $key . '][field_sub_article_type][' . $sub_article_type_lang . ']"]';
       
$form_sub['field_sub_article_title']['#states'] = array(
         
'invisible' => array(
           
$input => array('value' => 'body_only'),
            ),
          );
      }
    }
?>

Any help would be greatly apreciated !

My mistake, trying to be brief. Turns out that the entire $form trail is indeed needed :-)

So I replaced

<?php
        $form_sub
['field_sub_article_title']['#states'] = array(
         
'invisible' => array(
             
$input => array('value' => 'body_only'),
            ),
          );
?>

with

<?php
        $form
['field_sub_article'][$sub_article_lang][$key]['field_sub_article_title']['#states'] = array(
         
'invisible' => array(
             
$input => array('value' => 'body_only'),
            ),
          );
?>

Problem solved !

How can I use "or" condition in states, I've looked at all available examples on this page as well as on Fixed conditionals to allow OR and XOR constructions and form_example_states_form, but nothing seems to work.

When I've tried examples in Fixed conditionals to allow OR and XOR constructions, my page JS seems to be breaking.

I'm using Drupal Commerce Kickstart which has drupal version "7.22" and Kickstart version "7.x-2.9".

Here is sample form I'm using

<?php
$form
['field_a'] = array(
 
'#type' => 'select',
 
'#value' => t('select value'),
 
'#options' => array(10 => 'ABC', 11 => 'DEF', 12 => 'GHI', 13 => 'DEF', 14 => 'GHI'),
);
$form['field_b'] = array(
 
'#type' => 'select',
 
'#value' => t('Select value'),
 
'#options' => array(20 => 'ABC', 21 => 'DEF', 22 => 'GHI', 23 => 'DEF', 24 => 'GHI'),
 
'#states' => array(
   
'invisible' => array(
     
':input[name="field_a"]' => array('value' => 10), // I want this field to be invisible when field_a will have either 10 or 11
   
)
  )
);
?>

Yogesh

I got this working with above comment Negated values and OR lists.

Yogesh

If a field can be found at different depths in a form, or in multiple places, it may be difficult to get a selector that works. In my case, this was caused by using Inline Entity Forms. The solution was to get the field's full name attribute of the field and use that as its selector. To be used with a :input[name=""] selector.

I turned it into a function for others to use. Haven't actually tried the function itself, but it should work.. if not, at least you get the idea:

<?php
function _field_domname($field, $field_name) {
 
$parts = $feld[LANGUAGE_NONE]['#field_parents'];
 
$parts[] = $field_name;
 
$parts[] = LANGUAGE_NONE;
  return
array_shift($parts) . '[' . implode('][', $parts) . ']';
}
?>

Usage example:

<?php
$form
['field_foo'] = array(
 
'#type' => 'select',
 
'#options' => array('bar', 'baz'),
 
// …
);

$field_foo_domname = _field_domname($form['field_foo'], 'field_foo');

$form['field_bar'] = array(
 
'#type' => 'something',
 
// …
 
'#states' => array(
   
'visible' => array(
     
':input[name="' . $field_foo_domname . '"]' => array('value' => 'bar'),
    ),
  ),
);
?>

Err, that example obviously wouldn't work. The function is meant for hook_form_alter()'s $form data, it wouldn't work when building the form as in the example above.

But like I said, you get the idea!

I needed to specify the states for a field inside a hook_field_widget_form() which receives a $delta parameter.

I simply used the $delta parameter but there was one issue:

':input[name=field_multi[und]['.$delta.'][override][type]]'

wouldn't work, you have to escape the brackets inside :input[]

':input[name=field_multi\\[und\\]\\['.$delta.'\\]\\[override\\]\\[type\\]]'

You can also put quotes around your field name so you don't have to escape all the brackets.

e. g.:
':input[name="field_multi[und]['.$delta.'][override][type]"]'

Since 7.13 (I think), Drupal supports OR and XOR conditionals: FAPI #states: Fix conditionals to allow OR and XOR constructions

I've been able to get nested conditional statements (AND statements joined by OR statements) to work by doing the following:

<?php
// If you put the conditions in the array, it is an AND statement.
// elem1[value] == 'Some value' && elem2[value] == 'Some other value'
$form['element']['#states'] = array(
 
':input[name="elem1"]' => array('value' => t('Some value')),
 
// AND
 
':input[name="elem2"]' => array('value' => t('Some other value')),
);

// In order to make an OR statement, the #states array needs to have NUMERIC keys which point to subarrays:
// (elem1[value] == 'Some value') || (elem2[value] == 'Some other value')
$form['element']['#states'] = array(
  array(
':input[name="elem1"]' => array('value' => t('Some value'))),
 
// OR
 
array(':input[name="elem2"]' => array('value' => t('Some other value'))),
);

// Any OR statement can become an XOR statement by adding 'xor' to the array:
// (elem1[value] == 'Some value') xor (elem2[value] == 'Some other value')
$form['element']['#states'] = array(
 
'xor',
  array(
':input[name="elem1"]' => array('value' => t('Some value'))),
 
// XOR
 
array(':input[name="elem2"]' => array('value' => t('Some other value'))),
);

// The subarrays are then treated as AND statements:
// (elem1[value] == 'Some value' && elem3[checked]) || (elem2[value] == 'Some other value' && elem4[checked])
$form['element']['#states'] = array(
  array(
   
':input[name="elem1"]' => array('value' => t('Some value')),
   
// AND
   
':input[name="elem3"]' => array('checked' => TRUE)),
  ),
 
// OR
 
array(
   
':input[name="elem2"]' => array('value' => t('Some other value')),
   
// AND
   
':input[name="elem4"]' => array('checked' => TRUE)),
  ),
);
?>

If Alan Evans is correct about being able to negate statements with '!value' instead of 'value', that means you can write any boolean statement with #states. It might not be easy, though, because first you have to transform the logic into a collection of AND statements joined by OR statements. So brush up on your Boolean algebra (especially its laws, most especially De Morgan's laws).

Great examples, but you neglected to define the actual states themselves. You use:

<?php
$form
['element']['#states']
?>

instead of ie.:

<?php
$form
['element']['#states']['visible']
?>

If a simple form exists out of 3 sets of radio buttons:

  1. Show question 2? [ Yes | No ]
     
  2. Show question 3? [ Yes | No ]
    (This question only shows when question 1's answer equals Yes)
     
  3. Any question.
    (This question only shows when question 2's answer equals Yes)

Question 3 is only shown when 1 and 2 are answered with Yes, right? Now when I change the answer of question 1 to No, question 2 will be hidden, but question 3 is still visible!

For question 3 I used to use the following states:

<?php
'#states' => array(
 
'visible' => array(
   
':input[name=question2]' => array('value' => 'yes'),
  ),
),
?>

but now have to check all previous questions, ie.:

<?php
'#states' => array(
 
'visible' => array(
   
':input[name=question1]' => array('value' => 'yes'),
   
':input[name=question2]' => array('value' => 'yes'),
  ),
),
?>

Particularly on more complex forms this is not ideal. It would be great if we could also use the 'visible' state as a remote condition, ie.:

<?php
'#states' => array(
 
'visible' => array(
   
':input[name=question2]' => array('visible' => true),
   
':input[name=question2]' => array('value' => 'yes'),
  ),
),
?>

I fiddled a bit with adding a custom state, but so far, no good.

Am I missing something? Or does anyone have a solution for me? Thanks a lot in advance!

Here's an example on how to show a fieldset only if the value of a select box is selected / not empty.

The key is to use the "NOT" operator (e.g "!value"), like such:

<?php
     
'visible' => array(
       
':input[name="endpoint"]' => array('!value' => ''),
      ),
?>
<?php
  $form
['MODULE_api_test_response_settings'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Test Response Data'),
   
'#states' => array(
     
// Only show when endpoint select box is selected
     
'visible' => array(
       
':input[name="endpoint"]' => array('!value' => ''),
      ),
    ),
  );
?>

Cheers,
DT