1

I have a form with 3 sequential selects at the top, each populated as a result of the selection in the one prior.

After the third select is choice is made, a large number of fields segregated into two vertical tabs should be populated.

  1. In the form, I set the default value of each of the fields to an associative array element, which is empty when the form is first built.
  2. When the third select choice is made, its callback a. obtains an associative array of the values needed for the form b. stores them in $form_state['storage']['mykey'] c. sets $form['rebuild'] to true (for good measure) d. returns $form
  3. In the form function, prior to the field definitions, I set the associative array used for the default values to $form_state['storage']['mykey']

I can see in xdebug that $form_state has the data in the correct place prior to returning, but when execution returns to the form function, $form_state no longer has a ['storage'] key.

AJAX CALLBACK

function my_ajax_callback($form, &$form_state) {
  $form_state['storage']['draft'] = my_mysql_get($form_state['values']['id']);
  return $form;
}

FIRST LINE OF FORM FUNCTION (most fields use $r for default_value)

  $r =  !isset($form_state['storage']['draft']) ?  array() : $form_state['storage']['draft'];

A breakpoint at the callback reveals that it is reached, and that $form_state['storage']['draft'] is populated at the point of returning.

A breakpoint at the equate reveals that ['storage'] is no longer in $form_state, but this is only reached (in the form function) if I return only a field from the callback, not the entire form. I really need the entire form, because 17 of 20 fields on the form need to be rebuilt at that point, to get the default value into them.

FORM FIELD WITH AJAX CALLBACK

  $form['info'] = array(
    '#type' => 'select',
    '#title' => 'Player',
    '#prefix' => '<div id="info_select">',
    '#suffix' => '</div>',
    '#options' => info_options($form_state['values']['key1'], $form_state['values']['key2']),

    // ajax to bind this select to the players select
    '#ajax' => array(
      'event' => 'change',
      'callback' => 'my_ajax_callback',
      'wrapper' => 'my_form_id',
    ),
  • Drupal 6 or Drupal 7? For that 'storage' key it seems D6. – sanzante Mar 16 '15 at 18:52
  • D7. The key doesn't matter, 'storage' is just a standard name to use for a stash in there, but it getting overwritten is the mystery. – JAyenGreen Mar 16 '15 at 22:04
  • The key doesn't matter in D7, but in D6 you had to use storege, so I thought it could be D6. You should add your code here (or relevant parts) to see the problem, Form API can be tricky and actual code is needed to solve the issue. – sanzante Mar 17 '15 at 7:12
  • Yes, I understood. Sorry, ambiguous choice of a key. I've added what I believe is the applicable code. – JAyenGreen Mar 17 '15 at 12:09
  • Can you add the form element definition that made the AJAX callback? I suppose that my_ajax_callback is in ['#ajax']['callback'], but not sure. As long as the elment is a select there's no submit function related, right? – sanzante Mar 17 '15 at 12:22
1

The workflow in an AJAX callback with a non button element is:

  • Submit data is received, form is constructed and validated.
  • If no validation errors are detected form is rebuilt. This means that the form function is called again (the form that returns the form structure).
  • AJAX callback function is called. The task of this function is only to return the data to the browser. No persistent calculations should be done here.

You have to detect if tou are in an AJAX form request in your form function. If yes, calculate your date. Then, construct your form accordingly. Last, return the right form fragment (or the whole form) in your AJAX callback function.

Form function:

 $form['info'] = array(
    '#type' => 'select',
    '#title' => 'Player',
    '#prefix' => '<div id="info_select">',
    '#suffix' => '</div>',
    '#options' => info_options($form_state['values']['key1'], $form_state['values']['key2']),

    // ajax to bind this select to the players select
    '#ajax' => array(
      'event' => 'change',
      'callback' => 'my_ajax_callback',
      'wrapper' => 'my_form_id',
    ),

// If all selects have values get data to populate elements in vertical tabs.
if (all_select_have_values()) {
  $form_state['storage']['draft'] = my_mysql_get($form_state['values']['id']);
}

// Then add the rest of form elements. 
$r =  !isset($form_state['storage']['draft']) ?  array() : $form_state['storage']['draft'];
...

In callback just return your form:

function my_ajax_callback($form, &$form_state) {
  return $form;
}

You are losing the values in $form_state because you are adding data to the $form_state in the callback function. AJAX calls are procesed by ajax_form_callback(). This function first calls drupal_process_form(), and then the AJAX callback function. $form_state is saved (when condition apply) at the end of drupal_process_form function.

  • Not only are you absolutely correct in your analysis of my order being ***-backwards, but you've explained it in an absolutely clear manner. Thank you! – JAyenGreen Mar 17 '15 at 23:36
  • I changed the flow accordingly. I test that it is an ajax request, which results as true, then I populate the array used by the fields' #default_value, and just before I return the form in the form function I see that the default values are populated. I then hit the callback, where I still see the default values populated, then return the form, but the default values don't appear in the fields. – JAyenGreen Mar 18 '15 at 17:40
  • What's is shown in the input fields is what you see in #value. #default_value is used in previous steps to populate the #value data (along other data like $form_state['values']). Remember that #default_value is isghonred if there's data send by the user ($form_state['values']) – sanzante Mar 23 '15 at 9:25

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.