I am using hook_element_info to create a reusable widget for other modules I'm developing. The widget has dropdowns (SELECT elements) that get their value from a database, based on selections in other dropdowns. However, I run into a lot of "illegal choice" messages when, for example, clicking the "Preview" button (when I use the widget in a node form).

I think I'm just missing something in my #process function. The only functions I am implementing that the hook_element_info docs mention is the #process function that takes $element, $form_state, and $complete_form.

function city_chooser_form_process($element, &$form_state, $complete_form) {

  // states
  $options = array('A' => 'Alabama', 'N' => 'New Hampshire', 'C' => 'Colorado');
  
  $element['sc_container']['existing_site_fields']['existing_state']['#options'] = $options;
  
  // radio box
  if ( !empty($form_state['values']['existing_or_new_site']) ) {
    $element['sc_container']['existing_or_new_site']['#default_value'] = $form_state['values']['existing_or_new_site'];
  }

  $state = null;
  if ( !empty($form_state['values']['existing_state']) ){
    $element['sc_container']['existing_site_fields']['existing_state']['#default_value'] = $form_state['values']['existing_state'];
    $state = $form_state['values']['existing_state'];
  }

  if ( triggered_by($form_state, 'existing_state') ) {
    $state = $form_state['triggering_element']['#value'];
    drupal_set_message('Triggered by existing_state, got value of <strong>'.$state.'</strong>', 'status');
    
  }
  
  // If there's a state and it's 'C', populate the list with Colorado cities.
  if ( $state != null && $state == 'C' ) {
    $element['sc_container']['existing_site_fields']['existing_city']['#options'] = array('d' => 'Denver', 's' => 'Saratoga', 'g' => 'Guthriesville');
  }
  else {
    $element['sc_container']['existing_site_fields']['existing_city']['#options'] = array('Fictional Town 1', 'Fictional Town 2');
  }

  if ( !empty($form_state['values']['existing_city']) ) {
    $element['sc_container']['existing_site_fields']['existing_city']['#default_value'] = $form_state['values']['existing_city'];
  }
  
  if ( triggered_by($form_state, 'existing_state') ) {
    $form_state['rebuild'] = TRUE;
  }
    
  return $element;
} 

Comments

JohnWoltman’s picture

I've uploaded some images to illustrate the problem I'm having. It's very bizarre, so I hope the screenshots help.

Step 1 - Choose "Colorado": AJAX modifies the contents of the cities dropdown.
Step 2 - Click "Preview" button: Illegal choice message. State says the same (Colorado) but the Cities list is reset.
Step 3 - Click "Preview" button again: No illegal choice message, Cities list is correct but the selected city has been reset.

JohnWoltman’s picture

I've cleaned up and posted code if that helps. I'm still working on it, and can't figure it out so far.

JohnWoltman’s picture

I've read http://drupal.org/node/752056, which I was hoping would shed some light, but it didn't explain it. I rewrote my code from scratch, using Examples as a base, but it still gives me the "illegal choice" message.

jaypan’s picture

You can't use AJAX in Drupal forms like you do with regular forms. If any form elements are present in the submitted data that weren't part of the original form, Drupal gives the error you are getting. It's part of Drupal's form security.

You will need to use AHAH to do what you want.

Contact me to contract me for D7 -> D10/11 migrations.

JohnWoltman’s picture

I thought D7 called it AJAX instead of D6's AHAH, even though it works similar to D6. It returns HTML, not XML. The Examples module uses the D7's new #ajax element FAPI property, which replaced #ahah.

Here's relevant code from hook_element_info implementation so you can see what the elements are:

  // List of states for the "Existing Site" state <select>
  // The required #options key will be added in the #process callback.
  $existing_state = array(
    '#type' => 'select',
    
    '#ajax' => array(
      'callback' => 'site_chooser_existing_site_state_callback',
      'method'   => 'replace',
      'wrapper'  => 'existing_city_wrapper',
    ),   
    '#title' => t('State'),
  );

  // List of existing cities
  // The required #options key will be added in the #process callback.
  $existing_city = array(
    '#type' => 'select',

    '#prefix' => '<div id="existing_city_wrapper">',
    '#suffix'  => '</div>',
    '#title'   => t('City'),
  );
jaypan’s picture

Sorry, I didn't notice it was D7. You can ignore my last post as it may be entirely wrong. Or it may be entirely correct - I haven't done any D7 module development yet so I really don't know one way or the other. I'm waiting until there is an official release before I start playing with it.

Contact me to contract me for D7 -> D10/11 migrations.

JohnWoltman’s picture

No worries; I'm still searching for the answer. Using XDebug, it looks like the $form_state['values'] doesn't get properly filled out when clicking the node's "preview" or "save" buttons. This is puzzling. Is there a step I'm missing when manipulating the form in #process?

JohnWoltman’s picture