Having three fields on a form: year-from, year-to and a title, all textfields. The title field should be enabled if and only if either of the year fields is filled, and having considered that Form API's #disabled attribute cannot be used for this. So I've came up with this:

function mymodule_myform() {
  $form = array();
  $form['yearfrom'] = array(
    '#type' => 'textfield',
    '#title' => t('Year from'),
  );
  $form['yearto'] = array(
    '#type' => 'textfield',
    '#title' => t('Year to'),
  );
  $form['title'] = array(
    '#type' => 'textfield',
    '#title' => t('Title'),
    '#states' => array(
      'enabled' => array(
        '#edit-yearfrom' => array('filled' => TRUE),
        '#edit-yearto' => array('filled' => TRUE),
      ),
      'disabled' => array(
        '#edit-yearfrom' => array('empty' => TRUE),
        '#edit-yearto' => array('empty' => TRUE),
      ),
    ),
  );
}

This ensures that the form comes up with all the three fields being empty, and the title field being disabled (by jQuery, only on client-side). This ensures that when either of the year fields has anything in it, the title field becomes enabled. Speaking CS: both the enabler and disabler conditions are OR'ed together, but the disabler ones should be AND'ed.

But this solution also has a problem: if either of the year fields gets empty, the title field gets disabled, though it should be only getting disabled if both the year fields become empty. How should I address this?

In other words: the above example is quite good for an OR relation, but how to implement an AND relation between the (disabler) conditions?

Comments

rfay’s picture

There's no good way to do OR probably until D8 or contrib implements #735528: FAPI #states: Fix conditionals to allow OR and XOR constructions. AND is the basic technique.

IMO you should *not* be putting both 'enabled' and 'disabled' in there - Either is implied by the other. Make enabled or disabled the default state of the item, and use #states to make it switch to the other.

I don't know of any straightforward ways to do OR.

boobaa’s picture

…about the first and last paragraph, but both the "enabler" and "disabler" rules are needed. If FAPI gets #disabled = TRUE, the submit callback will get the #default_value as $form_state['values'], regardless what was submitted at HTTP level. In other words, if a input/widget gets enabled on the client-side (which is basically #states do, without altering the cached form on the server), anything that gets submitted from the browser gets overwritten by FAPI, since FAPI knows only about the #disabled control.

And this is the very same thing that causes both rules to be written out: for the aforementioned reasons one must not set #disabled on the FAPI level, but the user should see the (title) input/widget disabled even when the form gets rendered on her screen. This can be done by the "disabler" rule. Anyway, from this point on, the "enabler" rule is quite enough.

To sum things up: it looks like #states has two (big?) drawbacks, not only one. 1. It knows only about AND, but nothing about OR, XOR or anything else between the rules. 2. It does not alter the controls' status at the FAPI (DB) level (on the server), but only at the client.

Anyway, thanks for the feedback - though it looks like I'll have to invent something on my own for all the above reasons (and if it'll be something generally usable, most likely in a contrib).

jenlampton’s picture

A neat trick based on this comment by Alan Evans allows you to create an "OR" by ANDing together two NOTs. You can add multiple conditions to your states as long as all the array keys are unique, as follows:

// The following element is hidden for another field's values 1 and 2
$form['field_content_secondary']['#states'] = array(
  'visible' => array(
    ':input[name="' . $name . '"], nonsense-1' => array('!value' => (string) '1'),
    ':input[name="' . $name . '"], nonsense-2' => array('!value' => (string) '2'),
  ),
);

In the above, the NOTs are created by adding the ! before the value, and the ANDs are created by giving unique keys to each identifier, ':input[name="' . $name . '"], nonsense-1' and ':input[name="' . $name . '"], nonsense-2'.