8.2.x common.inc drupal_process_states(&$elements)
8.0.x common.inc drupal_process_states(&$elements)
8.1.x common.inc drupal_process_states(&$elements)
8.3.x common.inc drupal_process_states(&$elements)
7.x 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()
Renderer::doRender in core/lib/Drupal/Core/Render/Renderer.php
See the docs for ::render().
1 string reference to 'drupal_process_states'
RenderTest::testProcessAttached in core/tests/Drupal/KernelTests/Core/Render/RenderTest.php
Tests that we get an exception when we try to attach an illegal type.

File

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

Code

function drupal_process_states(&$elements) {
  $elements['#attached']['library'][] = 'core/drupal.states';
  // Elements of '#type' => 'item' are not actual form input elements, but we
  // still want to be able to show/hide them. Since there's no actual HTML input
  // element available, setting #attributes does not make sense, but a wrapper
  // is available, so setting #wrapper_attributes makes it work.
  $key = ($elements['#type'] == 'item') ? '#wrapper_attributes' : '#attributes';
  $elements[$key]['data-drupal-states'] = Json::encode($elements['#states']);
}

Comments

BOESbo’s picture

State REQUIRE dont work

Vendramini’s picture

States Required or Optional doesn't work.

kle’s picture

I found this first in YamlFormEntitySettingsForm.php (Module yamlform) and it does its job !

    $form['remote_checkbox1'] = ['#type' => 'checkbox', '#title' => 'enable me'];
    $form['remote_checkbox2'] = ['#type' => 'checkbox', '#title' => 'or enable me'];
    $form['remote'] = [
      '#type'   => 'textfield',
      // show field if "remote_checkbox1" OR "remote_checkbox2" is checked
      '#states' => [
        'visible' => [
          [':input[name="remote_checkbox1"]' => ['checked' => TRUE]],
          'or',
          [':input[name="remote_checkbox2"]' => ['checked' => TRUE]],
        ]
      ]
    ];
  • I think the array can be nested as you like it.
  • You can use 'or' and 'xor' (maybe more????)
  • maybe the colon is important, but ['input[...' is fine too!
tea.time’s picture

I've been long wondering how to implement OR'd conditions... totally not documented on this page. Great find.

Note that including the 'or' string itself in the array there is not required - that same structure of sub-arrays will default to OR if it's left out. (To use XOR logic, the string 'xor' must be included.) I found this and additional details on:

https://www.metaltoad.com/blog/drupal-7-form-api-using-states-multiple-c...

Checking out core/misc/states.js gives you the guts of the implementation, though I found it somewhat difficult to follow due to generic abstraction.

tresti88’s picture

I got required and visible to work together by doing the following:


      $form['my_select_list'] = [
        '#type' => 'select',
        '#options' => [
          'user' => t('User'),
          'group' => t('Group'),
        ],
      ];

      $form['auto_complete_field_0']['#states'] = [
        'visible' => [
          [':input[name="my_select_list"]' => ['value' => 'user']],
        ],
      ];

      $form['auto_complete_field_0']['widget']['0']['target_id']['#states'] = [
        'required' => [
          [':input[name="my_select_list"]' => ['value' => 'user']],
        ],
      ];

      $form['auto_complete_field_1']['#states'] = [
        'visible' => [
          [':input[name="my_select_list"]' => ['value' => 'group']],
        ],
      ];

      $form['auto_complete_field_1']['widget']['0']['target_id']['#states'] = [
        'required' => [
          [':input[name="my_select_list"]' => ['value' => 'group']],
        ],
      ];

The field containers are set to hidden/visible (which also hides/shows the actual input element)

The actual input elements themselves need to be targeted to make them required/optional

thinkinkless’s picture

This is a good nuance to understand - thanks! Targeting the widget worked for me on a number field. It gets ignored otherwise.

    // My number field is hidden unless repeat is selected
    $form['field_myfield_num']['#states'] = [
      'visible' => [
        ':input[name="field_myfield_select"]' => ['value' => (string) 'repeat'],
        ],
    ];

    // My number field is set as required when repeat is selected.
    $form['field_myfield_num']['widget']['0']['value']['#states'] = [
      'required' => [
        ':input[name="field_myfield_select"]' => ['value' =>  (string) 'repeat'],
      ],
    ];