I am trying to use #states => array( 'enabled' => ... ) or #states => array( 'disabled' => ... ) on form elements of #type 'radios' or 'checkboxes'.

When the state change conditions apply, the disabled attribute gets set on a div around the radio buttons / checkboxes. The radio buttons / checkboxes themselves are not enabled or disabled.

Comments

aspilicious’s picture

Status:Active» Postponed (maintainer needs more info)

Is this still an issue? I used states before (even with checkboxes) had no problem.

wiifm’s picture

Status:Postponed (maintainer needs more info)» Active

Still definitely an issue,

Running drupal 7.8 and I am looking to check a checkbox and disable it when another checkbox is ticked.

This is a snippet of the code:

$form['field_guides_tools_a_z']['#states'] = array(
  'checked' => array(
    '#edit-field-guides-tools-menu-und' => array('checked' => TRUE),
  ),
  'disabled' => array(
    '#edit-field-guides-tools-menu-und' => array('checked' => TRUE),
  ),
);

The jQuery looks as though it works, but the issue lies in the fact that the 'checked' and 'disabled' attributes get applied to the parent div rather than the input element. Here is the resulting HTML for the above form element, after the dependent checkbox is checked.

<div id="edit-field-guides-tools-a-z" class="field-type-list-boolean field-name-field-guides-tools-a-z field-widget-options-onoff form-wrapper" checked="true" disabled="true"><div class="form-item form-type-checkbox form-item-field-guides-tools-a-z-und">
<input type="checkbox" class="form-checkbox" value="1" name="field_guides_tools_a_z[und]" id="edit-field-guides-tools-a-z-und">  <label for="edit-field-guides-tools-a-z-und" class="option">Show in Guides &amp; Tools Full A-Z </label>

<div class="description">Check this to show in the Guides &amp; Tools Full A-Z</div>
</div>
</div>

This must be a bug in core, anyone have a patch or workaround for this?

acbramley’s picture

Can confirm the behaviour wiifm has described above.

wiifm’s picture

Issue tags:+FAPI #states

added tag

emosbaugh’s picture

I believe that the correct code should be as follows. This is because field_guides_tools_a_z when used with the field API is of field type "container." Does that solve your problem?

  $form['field_guides_tools_a_z']['und']['#states'] = array(
    'checked' => array(
      '#edit-field-guides-tools-menu-und' => array('checked' => TRUE),
    ),
    'disabled' => array(
      '#edit-field-guides-tools-menu-und' => array('checked' => TRUE),
    ),
  );
acbramley’s picture

@emosbaugh Wow thanks that fixed it! That's really strange though because we are also using the "visible" state for another checkbox and that works without the language key in there too. Could this be a bug?

acbramley’s picture

This should probably be documented somewhere as well, we tried finding a fix for this for hours and didn't get any hint that we needed to add the language key to the array.

emosbaugh’s picture

Status:Active» Closed (works as designed)
chx’s picture

Version:7.0-rc1» 7.x-dev
Component:forms system» documentation
Status:Closed (works as designed)» Active

Why was this closed down without a link to the updated documentation?

Cyberwolf’s picture

StatusFileSize
new738 bytes

I think my description of the issue was slightly misunderstood. Uploading an example module here that demonstrates it. Just enable it on your Drupal installation and go to the path issue/994360.

jhodgdon’s picture

Cyberwolf: are you saying that the fix in #5 doesn't work for you?

damien_vancouver’s picture

The same issue exists for type=select, and #5 fixed it for me. Thank you!

Cyberwolf’s picture

@jhodgdon: my example code does not use the field API, just the form API. If somebody would be able to take a look at my example code and tell me how it should be fixed if this is really expected behavior, you would make my day :)

NROTC_Webmaster’s picture

The doc's page is http://api.drupal.org/api/examples/form_example%21form_example_states.in...

The Examples module http://drupal.org/project/examples form_example_states.inc should have real examples though.

jhodgdon’s picture

Status:Active» Postponed (maintainer needs more info)

I don't understand this issue.

Is there a problem with the documentation? If so, what documentation needs to be changed, on what documentation page, and what should it say that it is currently not saying?

Or is there a bug in the code? If so, can you describe what is happening when, vs. what should be happening?

Or is this a support request? If so, please move it to the Drupal.org forums, or just change the category of this issue to "support request" and mark it with status "fixed".

Cyberwolf’s picture

I provided a sample module showing you what goes wrong. I have no idea what else I can do to make my point clear. This is not a support request, this is a bug report.

Copy pasting some code from the sample module which illustrates the problem:

$options = array();
  $option_range = range(1, 4);
  foreach ($option_range as $option_number) {
    $options['option' . $option_number] = t('Option !number', array('!number' => $option_number));
  }

  $form['disable_checkboxes'] = array(
    '#type' => 'checkbox',
    '#title' => t('Disable the checkboxes below'),
  );

  $form['checkboxes'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Checkboxes'),
    '#options' => $options,
    '#states' => array(
      'disabled' => array(
      ':input[name="disable_checkboxes"]' => array('checked' => TRUE),
      ),
    ),
  );

  $form['disable_radios'] = array(
      '#type' => 'checkbox',
      '#title' => t('disable the radios below'),
  );

  $form['radios'] = array(
    '#type' => 'radios',
    '#title' => t('radios'),
    '#options' => $options,
    '#states' => array(
      'disabled' => array(
      ':input[name="disable_radios"]' => array('checked' => TRUE),
      ),
    ),
  );

When the 'disable_checkboxes' checkbox input field is checked, the checkboxes should become disabled. When the 'disable_radios' checkbox input field is checked, the radios should become disabled. None of both works as expected. The disabled attribute gets set on a div around the radio buttons / checkboxes. The radio buttons / checkboxes themselves are not enabled or disabled.

damien_vancouver’s picture

@cyberwolf: Thanks for posting the code here.

I pasted it into a test form and try as I might, I was also unable to get the disabled/enabled to work. I did note that as per the form examples, "visible" does work for hiding/un-hiding groups of checkboxes. So this works fine:

<?php
  $form
['checkboxes'] = array(
   
'#type' => 'checkboxes',
   
'#title' => t('Checkboxes'),
   
'#options' => $options,
   
'#states' => array(
     
'visible' => array(
         
':input[name="disable_checkboxes"]' => array('checked' => FALSE),
      ),
    ),
  );
?>

and the checkboxes hide or re-appear based on the "disable checkboxes" box.

The issue is that you can't use 'enabled' and 'disabled' on type=checkboxes or type=radios. That is because these elements are simply wrapper divs in the HTML output, and so the disabled attribute gets set on the wrapper div itself, not the individual type=checkbox sub-items that are the checkboxes. Since those checkbox items do not get a disabled=true on them, they keep working. Using visible=false on the other hand, hides the entire contents of the outer div - so that works as one would expect.

In hopes I could make this work like it seems to for Field API fields (using hook_form_alter), I tried adding the LANGUAGE_NONE key ('und') in several places. I tried wrapping the checkboxes element inside a container element and setting #states on that (trying to replicate how field API seems to do it), but no luck. I tried setting the #states in hook_form_alter but still no luck.

My conclusion after seeing what is happening with disabled=true is that for it work like you are hoping we'd need improvements to misc/states.js, so that it was smart enough to figure out that it's been called on a type=checkboxes or type=radios wrapper div, and in that case apply the #states logic to each sub-element instead of/as well as the outer div. (so all four type=checkbox elements, :input[name="checkboxes[option1]"] through :input[name="checkboxes[option4]"] would get the state=disabled). I looked at misc/states.js and it is not doing anything even remotely like this suggestion yet, it's simply applying the states to the selectors in question. So this enhancement may not be fixable without API breakage (so therefore may only be fixable for Drupal 8).

If that's the case, then we need to at least update the documentation to make it clear you can't do this and why. If there is a simple workaround (perhaps some custom jQuery you could add with drupal_add_js) we could document that too. The documentation as it stands is a bit misleading in that it demonstrates enabled / disabled with single checkboxes (which works fine), and then demonstrates visible with groups of checkboxes (also works fine), without mentioning that enabled/disabled will not work in this case.

nod_’s picture

Version:7.x-dev» 8.x-dev
Component:documentation» forms system
Status:Postponed (maintainer needs more info)» Needs work
Issue tags:+JavaScript

Great work, thanks :) that makes it come back on the table for D8.

I'm assiging that for FAPI, it may be a solution to do something in form_process_checkboxes().

I don't think it'd be too much trouble to make that work in JS. If it's a JS only solution the part that needs to change is the function: $(document).bind('state:disabled', function(e) { }); in states.js line 490.

Cyberwolf’s picture

Thanks guys for finally taking a decent look at the issue and confirming it!

damien_vancouver’s picture

It was bothering me that I thought this worked for Field API fields and not Form API fields, so I recreated the test fields in a content type and then tried adding the #states array via hook_form_alter. But it turns out it was only working for me before because I was using a type=select, and it only worked for #6 above because acbramley was using a single checkbox. It definitely doesn't work for Field API type=checkboxes or type=radios for the same reasons as my testing in #17.
So that clears up any remaining mystery in my mind.

@nod_, it sounds like in your expert opinion the changes aren't that drastic. Since Field API isn't working like I thought, if we were able to improve states.js so that it quietly just handled this properly as the documentation would lead you to expect, then would it count as an API change for the purposes of backporting it?

My jQuery is way too terrible to attempt to write this as a patch, but how about this logic added to states.js $(document).bind('state:disabled', function(e) { ?

// ...
// existing .bind('state:disabled') logic here
// ...

// If the element is a container full of checkboxes or radios,
// each descendent checkbox or radio element must also have its state set.
if $(e.target) element type == "checkboxes" {
    apply .attr('disabled', e.value);   to all descendent elements of type=checkbox
} elseif  $(e.target) element type == "radios" {
    apply .attr('disabled', e.value);  to all descendent elements of type=radio
}

Sorry about the lame pseudocode... but am I correct in believing that's a fairly trivial js change? and if so would it be eligible for backport?

emosbaugh’s picture

Status:Needs work» Needs review
StatusFileSize
new914 bytes
PASSED: [[SimpleTest]]: [MySQL] 35,069 pass(es).
[ View ]

I think it can be handled in the preprocess functions of the radios and checkbox elements. Patch for drupal 8.x attached.

The issue with this patch is that the "disabled" attribute is still (as it is prior to this patch) applied to the outer div element (see markup below). This results in what I would think is invalid HTML markup. I would propose that with the #states element is unset to the original element in the process function or there is some filter in the js to not apply the attribute to the div element. This is a much larger change and I am hesitant to include it in this patch.

<div id="edit-radios" class="form-radios" disabled="disabled">
  <div class="form-item form-type-radio form-item-radios">
    <input type="radio" id="edit-radios-option1" name="radios" value="option1" class="form-radio" disabled="disabled">  <label class="option" for="edit-radios-option1">Option 1 </label>
  </div>
  <div class="form-item form-type-radio form-item-radios">
    <input type="radio" id="edit-radios-option2" name="radios" value="option2" class="form-radio" disabled="disabled">  <label class="option" for="edit-radios-option2">Option 2 </label>
  </div>
</div>
marcingy’s picture

Status:Needs review» Reviewed & tested by the community

This patch works nicely. I do however wonder if we need tests for this?

attiks’s picture

Status:Reviewed & tested by the community» Needs review
Issue tags:+Needs tests

Please add some tests so we can make sure this really works now and in the future.

jhodgdon’s picture

Status:Needs review» Needs work

If it needs tests, it's "needs work" status. :)

Alan D.’s picture

Maybe related, but I instantly bypassed this issue via an after_build without a second thought...

I'm trying to get the radios on the Diff module page to disable / hide if the corresponding one is set.

<?php
  $form
['diff']['old'] = array(
   
'#type' => 'radios',
   
'#options' => $revision_ids,
   
'#default_value' => $old_vid,
   
'#after_build' => array('diff_form_process_revision_radios'),
  );
 
$form['diff']['new'] = array(
   
'#type' => 'radios',
   
'#options' => $revision_ids,
   
'#default_value' => $new_vid,
   
'#after_build' => array('diff_form_process_revision_radios'),
  );

// using
function diff_form_process_revision_radios($element, $form_state) {
 
$parent = current($element['#parents']) == 'new' ? 'old' : 'new';
  foreach (
element_children($element) as $vid) {
   
$element[$vid]['#states'][] = array(
     
'disabled' => array(
      
':input[name="' . $parent . '"]' => array('value' => "$vid")
      )
    );
  }
  return
$element;
}
?>

Everything appears to be getting set, Drupal.settings {}, form names, ids, values, et al, but the only effect on the diff page is that the very first checkbox on the left at the top is disappearing after about 0.1 ms (about the time I would expect the 80 or so radios to take to be processed). So can radios trigger radios using states? I tried with visible, invisible, disabled.

Alan D.’s picture

Related to tests, are their any #state tests?

attiks’s picture

#26 only way to really test them is using javascript, we build testswarm and are currently testing the following for states (D7): http://drupalcode.org/project/testswarm.git/blob/refs/heads/7.x-1.x:/tes... (Live demo)

There's a Drupal 8 version as well but it's lagging a bit, but most tests will work for both D7 and D8.

pwaterz’s picture

I tried the patch above, and it ignores default values on the checkboxes element. It defaults them to be all unchecked. Here is the code that I used,

<?php
 $form
['select_all'] = array(
     
'#type' => 'checkbox',
     
'#title' => t('Select All'),
    );

   
$form['collections'] = array(
     
'#type' => 'checkboxes',
     
'#title' => isset($conf['checkbox_heading']) ? $conf['checkbox_heading'] : 'Alert me to articles on the following topics',
     
'#options' => $options,
     
'#default_value' => $default_value,
     
'#states' => array(
         
'checked' => array(
               
':input[name="select_all"]' => array('checked' => TRUE),
              ),
          ),
    );
?>
jaykainthola’s picture

Hi,

When I am using "#states" property for a first checkbox to checked when another checkbox is checked. All is working fine, except that default value of first checkbox is being ignore.

Can any one help me out?

Thanks

DuaelFr’s picture

Status:Needs work» Needs review
StatusFileSize
new829 bytes
PASSED: [[SimpleTest]]: [MySQL] 49,244 pass(es).
[ View ]
new773 bytes

I have the same issue on a select field.
After investigating a bit it was fully evident that setting the checked/disabled attribute on a wrapper could never do the job.

The attached patch is working well for me with select, checkboxes and textfields but #21 was not changing anything for me.
As far I know there is no tests on JS files so I turn this issue back to Needs Review.

This patch is part of the #1day1patch initiative.

DuaelFr’s picture

StatusFileSize
new364 bytes
new384 bytes
PASSED: [[SimpleTest]]: [MySQL] 53,086 pass(es).
[ View ]

Patch rerolled.

This patch is part of the #1day1patch initiative.

forko’s picture

I was trying to check checkbox... 2 days...
#5 worked for like charm...

Garrett Albright’s picture

Issue summary:View changes
jhedstrom’s picture

Status:Needs review» Needs work
Issue tags:+Needs re-roll, +Needs manual testing

This needs a re-roll, if still applicable. The checkbox logic has changed to this in states.js:

$(document).on('state:checked', function (e) {
    if (e.trigger) {
      $(e.target).prop('checked', e.value);
    }
  });
jhedstrom’s picture

Issue tags:-Needs re-roll+Needs reroll
DuaelFr’s picture

Status:Needs work» Needs review
Issue tags:-Needs reroll
StatusFileSize
new364 bytes
new347 bytes
PASSED: [[SimpleTest]]: [PHP 5.4 MySQL] 91,924 pass(es).
[ View ]

Reroll of #31
D7 patch is unchanged. D8 patch has been adapted.