I'm working on a patch for the Login Disable module, and on the settings page, I want to have two mutually exclusive options so that when one is checked, the other is unchecked. Using the FAPI #states value, I have the following code:

function login_disable_settings_form($form, &$form_state) {
  $form = array();

  $form['all_users'] = array(
    '#type' => 'fieldset',
    '#title' => t('All Users'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );

  $form['all_users']['login_disable_is_active'] = array(
    '#type' => 'checkbox',
    '#title' => 'Prevent user log in',
    '#description' => t('When active the user login form will be disabled for everyone. For roles granted bypass rights they must use the access key defined below.'),
    '#default_value' => (bool)variable_get('login_disable_is_active', FALSE),
    '#states' => array(
      'unchecked' => array(
        ':input[name="login_disable_individual"]' => array('checked' => TRUE),
      ),
    ),
  );

  $form['all_users']['login_disable_key'] = array(
    '#title' => t('Access key (optional)'),
    '#description' => t('For added security, a word can be required to be added to the URL.'),
    '#type' => 'textfield',
    '#size' => 10,
    '#default_value' => variable_get('login_disable_key', 'admin'),
  );
  if (!empty($form['login_disable_key']['#default_value'])) {
    $form['login_disable_key']['#description'] .= '<br />' . t('The URL to use to log in is: @url', array('@url' => url('user/login') . '?' . $form['login_disable_key']['#default_value']));
  }

  $form['all_users']['login_disable_message'] = array(
    '#title' => t('End-user message when login is disabled'),
    '#description' => t('The message to display to users when login has been disabled.'),
    '#type' => 'textfield',
    '#size' => 80,
    '#default_value' => variable_get('login_disable_message', t('Member access has been temporarily disabled. Please try again later.')),
  );

  $form['individual'] = array(
    '#type' => 'fieldset',
    '#title' => t('Indivdual'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );

  $form['individual']['login_disable_individual'] = array(
    '#type' => 'checkbox',
    '#title' => t('Disable on user profile'),
    '#description' => t('Checking this box allows disabling individual logins on user profile pages.'),
    '#states' => array(
      'unchecked' => array(
        ':input[name="login_disable_is_active"]' => array('checked' => TRUE),
      ),
    ),
  );

  $form = system_settings_form($form);

  return $form;
}

So as I see it, my code should just uncheck a checkbox when the other checkbox is checked. I also want the option that neither is checked. However, what's happening is that they are working as a toggle, in that if I uncheck one, the other one is being checked, which is prohibiting the third option of neither of them being checked. Can anyone tell me why I'm getting this effect?

Thanks.

up vote 1 down vote accepted

Form API states act in both directions. Suppose you set an element to be unchecked if a condition X is true. Then it gets unchecked when X becomes true, and gets non unchecked (i.e. checked) when X becomes false. See the example code and its demo at drupal.org.

You need some custom jQuery in the case.

Alex is right of course, in that the way the mechanism behaves is that it always toggles. Either it toggles between a TRUE/FALSE of some active state ("enabled", "checked", etc) or it toggles between a TRUE/FALSE of some inactive state ("disabled", "unchecked", etc).

This doesn't mean it's impossible to achieve what you're trying to do though; in order to do that you'll want to use the logical AND modifier by specifying the condition like this:

STATE_WE_WANT_TO_SWITCH_TO => array(
  REMOTE_ELEMENT => array(THE_STATE_THAT_TRIGGERS_THIS => true),
  LOCAL_ELEMENT => array(THE_STATE_WE_WANT_TO_GET_OUT_OF => true),
)

or alternately I think this would work too ... but I haven't tested to see if it goes in a loop.

STATE_WE_WANT_TO_SWITCH_OUT_OF => array(
  REMOTE_ELEMENT => array(THE_STATE_THAT_TRIGGERS_THIS => false),
  LOCAL_ELEMENT => array(THE_STATE_WE_WANT_TO_GET_OUT_OF => true),
)

where

STATE_WE_WANT_TO_SWITCH_TO = the state you want to switch your form element into, 
and NOT automatically toggle back out of again when the triggering thing gets switched back.
REMOTE_ELEMENT = the other form element whose state change triggers this action
THE_STATE_THAT_TRIGGERS_THIS = the state that element needs to be in, to trigger this
LOCAL_ELEMENT = this form element itself
THE_STATE_WE_WANT_TO_GET_OUT_OF = the state it needs to be in, in order for this action 
to be triggered. Setting this makes the statement evaluate to FALSE once 
it's happened once, making this a one-way action.

So in your case this becomes:

'#states' => array(
  'unchecked' => array(
    ':input[name="login_disable_is_active"]' => array('checked' => TRUE),
    ':input[name="login_disable_individual"]' => array('checked' => TRUE),
  ),
),

https://api.drupal.org/api/drupal/includes%21common.inc/function/drupal_process_states/7

  • I tried this and it did not work for me, sadly :( – Cheeso Aug 4 '16 at 19:36

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.