First, apologies if this ended up in the wrong place; the "forms component" seemed the closest match for submitting a Forms API bug.

I MUST be doing something wrong . . . or this is a bug in the Forms API.

I'm presently running a 5.1 Drupal install. Here's the problem I'm encountering - it's somewhat straight forward:

I try to set default values for a checkboxes field in a multistep form and the default values don't get applied.

This only appears to happen when:

1) The checkboxes field in NOT in the first form presented (i.e. not switch case 1), AND (I think)
2) The checkboxes field is also introduced in that subsequent case.

Here's a complete sample testform module that exhibits the behavior (apologies for the longish, and, likely ugly, sample - newbie programmer):

function testform_form($form_values=NULL) {

  $form['#multistep'] = TRUE;
  $form['#redirect'] = FALSE;

  if (isset($form_values['step'])) {
        $current_step = ++$form_values['step'];
  } else {
        $current_step = 1;
  }

  $form['step'] = array(
        '#type' => 'hidden',
        '#value' => $current_step,
  );
  switch($current_step) {

    case 1:
      $form['name'] = array(
        '#type' => 'textfield',
        '#title' => t('Name'),
        '#description' => t('test field');
      );

      $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
    break;

    case 2:
      $options = array('10' => t('Bobby'), '12' => t('Ralph'), '20' => t('George'));
      $defaults['test'][] = '12';

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

      $form['details']['admin'] = array (
        '#type' => 'checkboxes',
        '#title' => t('Only admin can view'),
        '#default_value' => $defaults['test'],
        '#options'  => $options,
      );

      $form['name'] = array(
        '#type' => 'textfield',
        '#title' => t('Name'),
        '#size' => 30,
        '#maxlength' => 64,
        '#description' => t('Enter the name for this group of settings'),
      );

      $form['submit'] = array('#type' => 'submit', '#value' => t('Continue'));
    break;
  }
 
return $form;

}

function testform_page() {
  return drupal_get_form('testform_form');
}

function testform_submit($form_id, $form_values) {
  switch($form_values['step']) {
    case 1:
      break;

    case 2:
      break;
  }
}

function testform_menu() {
  global $user;
  $items = array();
  $items[] = array('path' => 'testform',
                   'title' => t('Test Form'),
                   'callback' => t('testform_page'),
                   'access' => user_access('access content') && ($user->uid > 0),
                   'type' => MENU_CALLBACK
  );
  return $items;
}

If I use this same method of setting the default values WITHOUT putting it in a multistep form . . . NO PROBLEM; it sets the values in what I believe is the correct fashion (cue: bang forehead against wall):

function testform_form($form_values=NULL) {

  $options = array('10' => t('James'), '12' => t('Bobby'), '20' => t('George'));
  $defaults['test'][] = '12';

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

  $form['details']['admin'] = array (
    '#type' => 'checkboxes',
    '#title' => t('Only admin can view'),
    '#default_value' => $defaults['test'],
    '#options'  => $options,
  );

  $form['submit'] = array('#type' => 'submit', '#value' => t('Continue'));

  return $form;

}

function testform_page() {
  return drupal_get_form('testform_form');
}

function testform_submit($form_id, $form_values) {

}

function testform_menu() {
  global $user;
  $items = array();
  $items[] = array('path' => 'testform',
                   'title' => t('Test Form'),
                   'callback' => t('testform_page'),
                   'access' => user_access('access content') && ($user->uid > 0),
                   'type' => MENU_CALLBACK
  );
  return $items;
}

Somebody please, please, please tell me where I've gone wrong if this isn't a bug.

Comments

criticny’s picture

Title: Forms API MultiStep w/ Checkboxes: can't set default values » Ugly workaround
criticny’s picture

Title: Ugly workaround » re: Ugly workaround
criticny’s picture

Title: re: Ugly workaround » Forms API MultiStep w/ Checkboxes: can't set default values

I've removed some of these comments, as they were slightly misleading and suggested a non-solution.

If you're desperate (as I was/am), there's a completely ugly HACK that'll do the job of a "checkboxes" field until this is resolved - see the following comment.

Hope that saves someone out there the pain I had with this!

criticny’s picture

Now I'm just plain embarrassed . . . can I not delete or edit my follow-ups!? Argh

the hack won't work as a function. but it will if you just drop it into your code so . . . tired of this now.


  $options = array('10' => t('Charlie'), '12' => t('Ralph'), '20' => t('George'));
  $defaults['test']['12'] = '12';
  $label = t('These checkboxes pick up defaults');

  $optionCount =1;
  foreach ($options as $optionKey => $optionValue) {
    $checkboxArray = array(
      '#type'      => 'checkbox',
      '#title'     => t($optionValue),
      '#return_value'     => t($optionKey),
      '#attributes' => isset($defaults['test'][$optionKey]) ? array('checked' => 'checked') : NULL,
    );
    if ($optionCount == 1) {
      $checkboxArray['#prefix'] = '<div class="form-item"><label>' . $label . '</label><div class="form-checkboxes">';
    } elseif ($optionCount == count($options)) {
       $checkboxArray['#suffix'] = '</div></div>';
    }

    $form['details']['checkbox_' . $optionCount] = $checkboxArray;

    ++$optionCount;
  }

TBarregren’s picture

Thank you! I used your workaround in revision 1.10 of the RelatedContent module. Take a look at _relatedcontent_form_add_table(). It works like a charm.

It is worth pointing out for everyone else who is strugling with this very annoying bug, that it is the use of #attributes instead of the more proper #default_value that is the trick/hack. Very clever.

ashsc’s picture

I'm not sure if this relates to what you're doing but I found the following code in the api concerning checkboxes. It is an example of how they're implemented and shows how defaults are set. This is the same for D4.7->D6 as far as I can tell. I'm using D6:

<?php
$form['node_options_'. $node->type] = array(
  '#type' => 'checkboxes', 
  '#title' => t('Default options'), 
  '#default_value' => variable_get('node_options_'. $node->type, array('status', 'promote')),
  '#options' => array(
    'status' => t('Published'), 
    'moderate' => t('In moderation queue'), 
    'promote' => t('Promoted to front page'),
    'sticky' => t('Sticky at top of lists'),
    'revision' => t('Create new revision'),
  ),
  '#description' => t('Users with the <em>administer nodes</em> permission will be able to override these options.'),
);
?>

It would suggest that your code should look like this:

<?php
...
    case 2:
      $options = array('10' => t('Bobby'), '12' => t('Ralph'), '20' => t('George'));
      $defaults['test'][] = '12';

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

      $form['details']['admin'] = array (
        '#type' => 'checkboxes',
        '#title' => t('Only admin can view'),
        //'#default_value' => $defaults['test'], // changed to the line below...
        '#default_value' => variable_get('node_options_'. $node->type, $defaults['test']),
        '#options'  => $options,
      );
...
?>

I'm using this code and it seems to work for me. I found it here. I hope this helps somebody! I was looking for this info too. I'm sorry if I'm off topic.

MiMe’s picture

Version: 5.1 » 5.12

This has not been fixed with Drupal 5.12, it's still a BUG and it's been bugging me for a day now... so thanks to criticnc that coed works like a charm!

I tested examples from the forms_api_reference for select (#multiple => TRUE), radios and checkboxes... none of them worked in a multistep form, this is a VERY annoying bug indeed.

wim leers’s picture

This is a bug in D6 FAPI as well, see http://drupal.org/node/311651.

sutharsan’s picture

Version: 5.12 » 6.x-dev

Based on critincy's code I made a function out of it. It is used in a multistep form where the user can go forward _and_ backwards through the forms. Where going backwards, the checkbox default is set for a second time.

/**
 * Build checkboxes for multistep form.
 *
 * To work around core bug that checkbox default can only be set the first time.
 *
 * @param array $properties checkboxes properties.
 * @see drupal.org/node/187413
 */
function _mymodule_checkboxes($properties = array()) {
  $default = array(
    '#title' => 'checkboxes',
    '#options' => array(),
    '#default_value' => array(),
    '#tree' => TRUE,
    '#weight' => 0,
  );
  $properties = array_merge($default, $properties);
  $count = 1;

  // Set checkboxes form element properties.
  $form_element['#tree'] = $properties['#tree'];
  $form_element['#weight'] = $properties['#weight'];
  
  // Set array of checkboxes
  if (!empty($properties['#options'])) {
    foreach ($properties['#options'] as $key => $option) {
      $checkbox = array(
        '#type'         => 'checkbox',
        '#title'        => check_plain($option),
        '#return_value' => $key,
        '#attributes'   => isset($properties['#default_value'][$key]) ? array('checked' => 'checked') : NULL,
      );
      // Add prefix to first, suffix to last of checkboxes.
      if ($count == 1) {
        $checkbox['#prefix'] = '<div class="form-item"><label>' . $properties['#title'] . '</label><div class="form-checkboxes">';
      } elseif ($count == count($properties['#options'])) {
         $checkbox['#suffix'] = '</div></div>';
      }

      $checkbox_prefix = $properties['#tree'] ? '' : 'checkbox_';
      $form_element[$checkbox_prefix . $key] = $checkbox;
      ++$count;
    }
  }
  return $form_element;
}

Usage example:

        $form['accessories'] = _mymodule_checkboxes(
          array(
            '#title' => t('Accessories'),
            '#options' => $options,
            '#default_value' => $default,
            '#weight' => 4,
          )
        );

But, instead of making workarounds, this should be solved in core.

seanr’s picture

Priority: Normal » Critical

Sutharsan's right, of course. This bug has been around a while and strikes me as a fairly big one. Is anyone actively wortking on patching core? I unfortunately have no idea where to start.

Here's my D6 workaround for a multistep node form:

/**
 * Implementation of hook_form_alter().
 *
 * When first adding the node, put the 'Title' field on its own page in a
 * multistep node form.  We need to do this in hook_form_alter() itself
 * instead of a form_id-specific form alter so that we're likely to come after
 * everyone else's alterings, e.g. taxonomy, menu, etc.
 */
function example_form_alter(&$form, $form_state, $form_id) {
  $type = node_get_types('type', $node);
  if ($form_id == 'example_node_form') {
    if (arg(1) != 'add') {
      $node = $form['#node'];
    }
    
    if (isset($node->section)) {
      $section = $node->section;
    }
    elseif (isset($form_state['section'])) {
      $section = $form_state['section'];
    }
    elseif (isset($form_state['values']['section'])) {
      $section = $form_state['values']['section'];
    }
    
    if (empty($section)) {
      [step 1 fields here]
    }
    else {
      [step 2 fields here]
      
      // Add a submit handler to copy the version values from
      // $form_state['storage'] to $form_state['values']
      $form['#submit'][] = 'crmapi_node_form_submit';
      // For the actual submit button, add a handler to clear out
      // $form_state['storage'] entirely so that we actually submit the form.
      $form['buttons']['submit']['#submit'][] = 'crmapi_node_form_final_submit';
      
      if (arg(1) == 'add') {
        // Fix FUBAR Drupal handling of chewckboxes in multistep forms
        _example_form_checkboxes($form);
      }
    }
  }
}


/*
 * Helper function to step through form elements and fix default
 * values for checkboxes.
 */
function _example_form_checkboxes(&$form) {
  foreach (element_children($form) as $child) {
    if ($form[$child]['#type'] == 'checkbox') {
      if ($form[$child]['#default_value'] == TRUE) {
        $form[$child]['#attributes'] = array('checked' => 'checked');
      }
    }
    elseif ($form[$child]['#type'] == 'fieldset') {
      foreach (element_children($form[$child]) as $grandchild) {
        if ($form[$child][$grandchild]['#type'] == 'checkbox') {
          if ($form[$child][$grandchild]['#default_value'] == TRUE) {
            $form[$child][$grandchild]['#attributes'] = array('checked' => 'checked');
          }
        }
      }
    }
  }
}

It's really not recursive, but it works well enough for the standard node forms (I don;t think I've seen too many node forms with nested fieldsets)

dpearcefl’s picture

Status: Active » Postponed (maintainer needs more info)

Is this still an issue in modern Drupal?

dpearcefl’s picture

Status: Postponed (maintainer needs more info) » Active
stephenrobinson’s picture

Issue summary: View changes

still having issues in Drupal 7.31

Status: Active » Closed (outdated)

Automatically closed because Drupal 6 is no longer supported. If the issue verifiably applies to later versions, please reopen with details and update the version.