Form #states fixes
The core_states_conditions_fix helper adjusts how Drupal core's #states compares values, mainly so that conditions on multiple selects work as expected. It's based on the overrides in Conditional Fields.
The helper is on when the Helper module is installed, but off on sites that installed it before this helper was added. It adds js/states-conditions-fix.js to core's core/drupal.states library, so the fixes are loaded on every page that uses #states, with nothing to attach. Saving the helper settings clears the cached library definitions, so turning it on or off takes effect straight away.
Note
views_exposed_states_bundle_filter_fields needs these fixes for a multiple select bundle filter. If this helper is turned off, it adds no #states to that view's exposed filters, and logs an error.
Warning
The fixes replace core's comparison functions, so they change every #states condition on the site. Each change only makes a condition match where core's comparison never would, so conditions that already work keep working.
What changes
A value matches any selected option of a multiple select
In core, a value condition on a multiple select never matches, since the select's value is an array of the selected options. With the fix, a string value matches when it's one of the selected options.
// Show the "Subtitle" filter when "Article" is one of the selected content
// types, alone or with others.
$form['subtitle']['#states']['visible'] = [
':input[name="type[]"]' => ['value' => 'article'],
];
To match any of several options, list them with or:
$form['subtitle']['#states']['visible'] = [
[':input[name="type[]"]' => ['value' => 'article']],
'or',
[':input[name="type[]"]' => ['value' => 'news']],
];
empty works for multiple selects
Core checks empty by comparing the value to an empty string, which is never true for a multiple select. With the fix, a multiple select with nothing selected is empty, and filled is its opposite.
// Show the "Subtitle" filter while no content type is selected, meaning any.
$form['subtitle']['#states']['visible'] = [
':input[name="type[]"]' => ['empty' => TRUE],
];
Array values match the exact selection
An array value still matches when exactly those options are selected, in any order, as in core. The values are compared as strings, so numeric option keys like [5, 7] match the "5" and "7" the browser reports. An empty array matches nothing selected.
// Show the field only when exactly "Article" and "Page" are selected.
$form['field']['#states']['visible'] = [
':input[name="type[]"]' => ['value' => ['article', 'page']],
];
Regular expressions
A regex value matches when the value, or any selected option of a multiple select, matches the regular expression. flags is optional and uses JavaScript's flags, such as i for case-insensitive.
// Show the field when the title starts with "Report".
$form['field']['#states']['visible'] = [
':input[name="title"]' => ['value' => ['regex' => '^report', 'flags' => 'i']],
];
Exclusive or
An xor value matches when an odd number of the listed options is selected, for example exactly one of two.
// Show the field when "Article" or "Page" is selected, but not both.
$form['field']['#states']['visible'] = [
':input[name="type[]"]' => ['value' => ['xor' => ['article', 'page']]],
];
Lines of a textarea
A lines value matches when a textarea's non-empty lines are exactly the listed values, in any order. An empty list matches an empty textarea.
$form['field']['#states']['visible'] = [
':input[name="allowed_values"]' => ['value' => ['lines' => ['red', 'blue']]],
];
Leading and trailing line breaks are ignored
When a string value is compared, line breaks at the start or end of either string are ignored, so a textarea's trailing newline doesn't stop it from matching.
Better Exposed Filters' "Select all/none"
Better Exposed Filters can add a Select all/none link to checkboxes. It checks or unchecks every checkbox, but only tells the first one that it changed, and with jQuery 4 (Drupal 11.3 and later) its :first selector fails, so none of them are told. #states conditions on the checkboxes would still see their old values. With the fix, #states reads all of them after the link is clicked. It doesn't submit an auto-submit form again.
Compatibility
- The fix is installed as soon as the script loads, not in a behavior, so it's in place before core's
statesbehavior first evaluates the form. Core runs behaviors in the order they were added, so a behavior can't reliably run first. - Conditional Fields replaces the same comparisons in a behavior, which runs after this script loads. On pages where both are loaded, Conditional Fields' versions are used. They work the same way for multiple selects, but an array
valuematches when all the listed options are selected, even if others are too.