function drupal_process_states

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 4647
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'".

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.

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.

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.