6

I would like to translate some jquery .change functionality to use #states in the form api.

var radio1 = jQuery('input:radio[name=radio_1]');
var radio2 = jQuery('input:radio[name=radio_2]');

radio1.change(function() {
if (radio1.filter(':checked').val() == 0) {
  radio2[1].checked = true;
}
else {
  radio2[0].checked = true;
}
});

radio2.change(function() {
if (radio2.filter(':checked').val() == 0) {
  radio1[1].checked = true;
}
else {
  radio1[0].checked = true;
}
});

Logic:
If radio1 value 0 is selected then radio2 value 1 will be selected.
If radio1 value 1 is selected then radio2 value 0 will be selected.
if radio2 value 0 is selected then radio1 value 1 will be selected.
if radio2 value 1 is selected then radio1 value 0 will be selected.

Looking at form_example_states_form() shows that the syntax is:

'#states' => array(
  'action_to_take_on_this_form_element' => array(
    'jquery_selector_for_another_element' => array(
      'condition_type' => value,
    ),
  ),
),

What's missing for me is "action_to_take_on_this_form_element". I don't know how to select value 0 or 1 of the radio button; all I have in terms of viable options is checked/unchecked (see drupal_process_states() for a list).

What I currently have

'#states' => array(
  'checked' => array(
    ':input[name="radio_2"]' => array(
      'value' => 0,
    ),
  ),
  'unchecked' => array(
    ':input[name="radio_2"]' => array(
      'value' => 1,
    ),
  ),
),

and this doesn't seem to work. I need something like checked[0] in order to select the value 0 radio button.

Edit: Here is the working code. Would like to not use inline js.

function your_form(&$form, $form_state) {
  $form['radios1'] = array(
    '#type' => 'radios',
    '#title' => 'Radios 1',
    '#options' => array(
      0 => 'zero',
      1 => 'one',
    ),
    '#default_value' => 0,
  );

  $form['radios2'] = array(
    '#type' => 'radios',
    '#title' => 'Radios 2',
    '#options' => array(
      0 => 'zero',
      1 => 'one',
    ),
    '#default_value' => 1,
  );

  $form['#attached']['js'][] = array(
    'data' => "var radio1 = jQuery('input:radio[name=radios1]');
var radio2 = jQuery('input:radio[name=radios2]');

radio1.change(function() {
if (radio1.filter(':checked').val() == 0) {
  radio2[1].checked = true;
}
else {
  radio2[0].checked = true;
}
});

radio2.change(function() {
if (radio2.filter(':checked').val() == 0) {
  radio1[1].checked = true;
}
else {
  radio1[0].checked = true;
}
});",
    'type' => 'inline',
  );

  return $form;
}

Demo video: https://www.youtube.com/watch?v=lXfMFaTTVes

  • Is this for radio or radios ? – AyeshK May 28 '15 at 3:23
  • the radios type – mikeytown2 May 28 '15 at 4:13
6
+50

You can use the form_api #after_build property to set a callback where you can manipulate the individual radio button elements (not the grouped "radios" element).

function your_form(&$form, $form_state) {
  $form['radios1'] = array(
    '#type' => 'radios',
    '#title' => 'Radios 1',
    '#options' => array(
      0 => 'zero',
      1 => 'one',
    )
  );

  $form['radios2'] = array(
    '#type' => 'radios',
    '#title' => 'Radios 2',
    '#options' => array(
      0 => 'zero',
      1 => 'one',
    )
  );

  $form['#after_build'] = array('your_form_after_build_function');

  return $form;
}

function your_form_after_build_function($form) {
  $form['radios1'][0]['#states'] = array(
    'checked' => array(
      ':input[name="radios2"]' => array('value' => 1),
    ),
  );

  $form['radios1'][1]['#states'] = array(
    'checked' => array(
      ':input[name="radios2"]' => array('value' => 0),
    ),
  );

  $form['radios2'][0]['#states'] = array(
    'checked' => array(
      ':input[name="radios1"]' => array('value' => 1),
    ),
  );

  $form['radios2'][1]['#states'] = array(
    'checked' => array(
      ':input[name="radios1"]' => array('value' => 0),
    ),
  );

  return $form;
}
  • Close but this is the result I get: youtube.com/watch?v=Z5LZDjuqVIY This is what the original JS gives me: youtube.com/watch?v=lXfMFaTTVes – mikeytown2 Jun 1 '15 at 19:02
  • I can confirm that in my testing there is a bug, but not the same one you are encountering in your video. I'm finding that it works perfectly at first but in some circumstances when you change one of the sets of radio buttons and the following change to the other set of radio buttons doesn't trigger the state change. It looks like when changing the second radio set it compares the changed state to an incorrect "old value". I think this may be a bug in core, although it could be that adding states to individual radio form elements in a "radios" element via #after_build isn't supported. – Paul Querol Jun 2 '15 at 10:03
  • I'm pretty sure it works fine with normal checkboxes but radios are differents, that could explain why checked state is behaving that way. – GwenM Jun 2 '15 at 10:25
  • 1
    I've just raised an issue for this on drupal.org: drupal.org/node/2498791 – Paul Querol Jun 2 '15 at 10:27
2

Since #states is bugged for radios at the moment, another option might be to use an #ajax callback, as long as the extra server requests don't cause a problem for you:

function my_form($form, &$form_state) {
  $form = array(
    'radios1' => array(
      '#title' => 'Radios 1',
      '#type' => 'radios',
      '#options' => array(0 => 'Zero', 1 => 'One'),
      '#prefix' => '<div id="radios1-ajax-wrapper">',
      '#suffix' => '</div>',
      '#ajax' => array(
        'callback' => '_my_ajax_callback',
        'wrapper' => 'radios2-ajax-wrapper',
      ),
    ),
    'radios2' => array(
      '#title' => 'Radios 2',
      '#type' => 'radios',
      '#options' => array(0 => 'Zero', 1 => 'One'),
      '#prefix' => '<div id="radios2-ajax-wrapper">',
      '#suffix' => '</div>',
      '#ajax' => array(
        'callback' => '_my_ajax_callback',
        'wrapper' => 'radios1-ajax-wrapper',
      ),
    ),
  );

  if (isset($form_state['triggering_element'])) {
    switch ($form_state['triggering_element']['#name']) {
      case 'radios1':
        unset($form_state['input']['radios2']);
        if ($form_state['values']['radios1'] == 0) {
          $form['radios2']['#default_value'] = 1;
        }
        elseif ($form_state['values']['radios1'] == 1) {
          $form['radios2']['#default_value'] = 0;
        }
        break;

      case 'radios2':
        unset($form_state['input']['radios1']);
        if ($form_state['values']['radios2'] == 0) {
          $form['radios1']['#default_value'] = 1;
        }
        elseif ($form_state['values']['radios2'] == 1) {
          $form['radios1']['#default_value'] = 0;
        }
        break;
    }
  }

  return $form;
}

function _my_ajax_callback($form, &$form_state) {
  switch ($form_state['triggering_element']['#name']) {
    case 'radios1':
      return $form['radios2'];

    case 'radios2':
      return $form['radios1'];
  }
}

I wouldn't recommend this as a long-term solution though.


My original idea below suffers from the same #states bug as encountered when using an #after_build function, hence the strikethrough.

Given that the #after_build method is slightly bugged with the radios type, an alternative method that seems to work for me is to build the form out of individual radio elements and change the radio groupings using #attributes. That way you can set #states in your form constructor without needing an #after_build function:

function my_form($form, &$form_state) {
  $form = array(
    'radios1' => array(
      '#type' => 'container',
      '#prefix' => '<label for="edit-radios1">Radios 1</label>',
      '#tree' => TRUE,
      1 => array(
        '#type' => 'radio',
        '#title' => 'One',
        '#return_value' => 1,
        '#attributes' => array(
          'name' => array(
            'radios1',
          ),
        ),
        '#states' => array(
          'checked' => array(
            ':input[name="radios2"]' => array('value' => 2),
          ),
        ),
      ),
      2 => array(
        '#type' => 'radio',
        '#title' => 'Two',
        '#return_value' => 2,
        '#attributes' => array(
          'name' => array(
            'radios1',
          ),
        ),
        '#states' => array(
          'checked' => array(
            ':input[name="radios2"]' => array('value' => 1),
          ),
        ),
      ),
    ),
    'radios2' => array(
      '#type' => 'container',
      '#prefix' => '<label for="edit-radios2">Radios 2</label>',
      '#tree' => TRUE,
      1 => array(
        '#type' => 'radio',
        '#title' => 'One',
        '#return_value' => 1,
        '#attributes' => array(
          'name' => array(
            'radios2',
          ),
        ),
        '#states' => array(
          'checked' => array(
            ':input[name="radios1"]' => array('value' => 2),
          ),
        ),
      ),
      2 => array(
        '#type' => 'radio',
        '#title' => 'Two',
        '#return_value' => 2,
        '#attributes' => array(
          'name' => array(
            'radios2',
          ),
        ),
        '#states' => array(
          'checked' => array(
            ':input[name="radios1"]' => array('value' => 1),
          ),
        ),
      ),
    ),
  );

  return $form;
}

Note that I've changed your "Zero or One" radios to "One or Two" for this example because another bug in core means a radio with a value of 0 always gets checked when the form is built. If you absolutely must use a value of 0 then you'll need to work around that bug in some way.

  • Still no dice; this is a video of that form: youtube.com/watch?v=HYN6epYIKco – mikeytown2 Jun 4 '15 at 0:00
  • Hi mikeytown, can I just check if you've still got any of your own javascript modifying the values? It's just that in your videos you seem to be getting different results to morbiD and I (based on morbiD's comment at drupal.org/node/2498791#comment-9986937). It could also be that some other module you have installed is affecting the result. – Paul Querol Jun 4 '15 at 8:19
  • jQuery update 1.10 appears to be the issue. New video. It still has issues with radio 1 though. youtube.com/watch?v=VF70Czizbdk Issues start to show up at 1.7, 1.5 seems to work like core – mikeytown2 Jun 4 '15 at 17:27
  • @PaulQuerol See last comment – mikeytown2 Jun 4 '15 at 17:34
  • 1
    Hi @mikeytown, yes that's the behaviour that morbiD and I have experienced. Looks like it's down to the bug in core. So I'd say your options currently are: 1. Wait for a potential resolution on that bug, 2. Go with it "as-is" (i.e. mostly working but slightly buggy), or 3. go back to the alternative plan of using custom javascript. – Paul Querol Jun 5 '15 at 12:00
1

Have a look here:

Instead, you must use 'value' => 'name', where 'name' is the name of the radio option that you require to be selected. In your example it could be '1' instead on 1.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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