I have a problem with setting the default value of my 'radios' button, dynamically. In my code, I have an addresses radios group stated like this:

$form['addresses'] = array(
      '#type' => 'radios',
      '#title' => '',
      '#options' => $addresses,
      isset($user->data['NewAddress']) ? $user->data['NewAddress'] : 0,
    );

This works, however, in my form, I have an ajax callback that lets the user add a new address and this address also gets added to the form once they save the address, via ajax. I want to now set the default value to this new address. The default_value, if I do a dd() of it, shows that it is set to the new address, however, it just doesn't get selected.

I have read somewhere, that the default_value only works the first time that a form is loaded. My situation is, the form loads once, then all the changes are being made via Ajax. I do a $form_state['rebuild'] = true; after each new address is saved, however. So, basically, my question is, is there any way to get around this issue. How can we set the default_value each time a new address is entered via Ajax?

Any help would be greatly appreciated. Thanks.

Comments

Jaypan’s picture

  if(isset($form_state['values']['NewAddress']))
  {
    $default_address = $form_state['values']['NewAddress'];
  }
  elseif(isset($user->data['NewAddress']))
  {
    $default_address = $user->data['NewAddress'];
  }
  else
  {
    $default_address = 0;
  }
  $form['addresses'] = array(
      '#type' => 'radios',
      '#title' => '',
      '#options' => $addresses,
      '#default_value' => $default_address, // or maybe array($default_address)
    );
shabana.navas’s picture

Thanks for the example, but unfortunately, it is still not making any effect. A dd($default_value) and dd($form['addresses']['#default_value'] shows that the default value is the new address value, but it just isn't getting selected. Wonder, why there is a problem with the select button moving from the old address to the newly-entered address. Seems simple enough...

Shabana Navas
snavas@acromedia.com
Software Developer
Acro Media
https://www.acromedia.com
1-800-818-4564
Skype: super.shaba

Jaypan’s picture

I don't know what to say. I've used the above many times, both in #ahah in D6, and #ajax in D7, and it works fine. I'm guessing it's something else in your code, but cannot tell from the bit you've given.

shabana.navas’s picture

After further debugging of the radios button, I have found out that, once a new address is entered via Ajax (a div with the old address radios is replaced with a div with the new address included), the #default_value is set to the new address, however, the #value remains the same, pointed to the initial default address during form load. This is how the addresses radios array looks for a particular address:

[48720] => Array
  (
    [#default_value] => 48722
    [#value] => 48721
  )

As you notice, the #default_value is set to the new address, but the #value is the same. Do you think this has anything to do with the default value not getting checked? I tried setting the #value to the new address, as well, but that didn't work. Also tried using a #value_callback function to set it to the new address, but still no luck. Any ideas, as to why it would keep the #value the same despite rebuilding the form after every new address has been inserted?

UPDATE!! I wanted to test if the #value was the one that was causing the new address to be not selected, so what I did (as much as I don't like doing this) is, I manually, changed the #value for each option in the addresses radio box to the default value to see if that made any difference. Well, presto, it worked! The new address is being selected as the default address now. This is the code that I did:

//We have to set the #value of each address in the radio box to the default_value as well, otherwise, the new address won't get checked as the default
foreach($form['addresses']['radios']['#options'] as $key => $value) {
  $form['addresses']['radios'][$key]['#value'] = isset($user->data['NewAddress']) ? $user->data['NewAddress'] :  $default_value;
}

So now the question is, why would the #value remain the same? Even though, after each rebuild, we are populating the radio button from scratch. Also, note, I tried to unset the radio button after each new address was submitted, but that didn't work either.

Shabana Navas
snavas@acromedia.com
Software Developer
Acro Media
https://www.acromedia.com
1-800-818-4564
Skype: super.shaba

Jaypan’s picture

#value cannot be overridden, no matter what the user enters. If the user submits 3, and #value is set to 2, then the submitted value will be 2 regardless. If you set #default_value and #value, #default_value has no meaning since #value is the value.

shabana.navas’s picture

I think that the #value is setting every time that a new address is saved. Basically, my Ajax button for saving a new address is of type "submit", I am thinking this submits the form. My button definition is like this:

//Add the submit button to save the new address in the database and replace the address div with the new address added
    $form['save'] = array(
      '#type' => 'submit',
      '#name' => 'Save',
      '#value' => 'Save',
      '#ajax' => array(
        'callback' => 'NewAddress_Save_ajax_callback',
        'wrapper' => 'NewAddress_wrapper',
        'method' => 'replace',
      ),
      '#submit' => array('NewAddress_submit'),
      '#validate' => array('NewAddress_validate'),
    );

Is it because it is type "submit" that the #value, which is the initial #default_value, is being set each time I save a new address. I guess, we can change the button to #type "button", but if I understand it correctly, a button, which is of #type "button" will not call the #submit handler, and thus, my "NewAddress_submit" function won't get called. If so, then what would be the way around this?

How can we basically, NOT get the radios button value to submit each time we save an address?

Shabana Navas
snavas@acromedia.com
Software Developer
Acro Media
https://www.acromedia.com
1-800-818-4564
Skype: super.shaba

Jaypan’s picture

Can you show your form definition?

shabana.navas’s picture

I have copied and pasted all the relevant form definitions for this radios problem:

  //Get the addresses that this user has under their uid from the database
  $result = db_query("SELECT add_id, add_First_Name, add_Last_Name, add_Address, add_City, add_State, add_Zip_Code, add_Country
    FROM {addresses}
    WHERE uid = :uid", array(
    ':uid' => $node->uid
  ));
  
  //Fieldset for the addresses, this is being themed, as we want each address radio button
  //to be followed by its own edit and delete buttons
  $form['addresses'] = array(
    '#type' => 'fieldset',
    '#theme' => 'theme_Addresses',
    '#prefix' => '<div id="Addresses_wrapper">',
    '#suffix' => '</div>',
  );
  
  //Check to see if there are any addresses for this user
  if ($result->rowCount()) {   
    
    //Initialize the addresses array to enter all the available addresses for this user
    $addresses = array();
    //Just a counter to keep track of the default value for the radio button, basically the first address should be selected
    $count = 0;
    
    //Now store all the addresses available for this user in the addresses array
    foreach ($result as $address) {
      
      $addresses[$address->add_id] = '<b>' . $address->add_first_name . ' ' .      
      address->add_last_name . '</b><br>' .
      $address->add_address . ',' . '<br>' .
      $address->add_city . ',' . '<br>' .
      $address->add_state . ',' . '<br>' .
      $address->add_zip_code . '<br>' .
      $address->add_country;
    }

        //Add the edit button for this address
        //Also adds the ajax callback, basically this would be like entering a new address only
        //that we retrieve the address values from the database and insert it into the textfields as the default_value
        $form['addresses']['editaddress' . $address->add_id] = array(
          '#type' => 'button',
          '#value' => 'Edit',
          '#name' => 'Edit Address' . '-' . $address->add_id,
          '#ajax' => array(
            'callback' => 'EditAddress_ajax_callback',
            'wrapper' => 'EditAddress_wrapper-' . $address->add_id,
            'method' => 'replace',
          ),
          '#prefix' => '<div id="EditAddress_wrapper-' . $address->add_id . '">',
          '#suffix' => '</div>',
          '#limit_validation_errors' => array(),
          '#submit' => array(),
          '#weight' => 20,
        );
        
        //Add the delete button for this address
        $form['addresses']['deleteaddress' . $address->add_id] = array(
          '#type' => 'button',
          '#value' => 'Delete',
          '#name' => 'Delete Address' . '-' . $address->add_id,
          '#ajax' => array(
            'callback' => 'DeleteAddress_ajax_callback',
            'wrapper' => 'DeleteAddress_wrapper-' . $address->add_id,
            'method' => 'replace',
          ),
          '#prefix' => '<div id="DeleteAddress_wrapper-' . $address->add_id . '">',
          '#suffix' => '</div>',
          '#limit_validation_errors' => array(),
          '#submit' => array(),
          '#weight' => 20,
        );
        
        //Save the first address id, to make it as the default value for the radio buttons
        if ($count == 0) {
          $default_value = $address->add_id;
        }
        $count++;
    }
     
    //Add all the addresses to the radios button
    $form['addresses']['radios'] = array(
      '#type' => 'radios',
      '#title' => '',
      '#options' => $addresses,
      '#default_value' => isset($user->data['NewAddress']) ? $user->data['NewAddress'] : $default_value,
    );
		
	//Display the fields when the 'Add_Address' form state is 1
	if ($form_state['Add_Address'] == 1) {
    
    //Fieldset, container for the new address fields
    $form['newaddress']['fieldset'] = array(
      '#title' => t("Please enter your details"), 
      '#prefix' => '<div id="NewAddress_wrapper">', 
      '#suffix' => '</div>', 
      '#type' => 'fieldset',
    );
    
    //Display the fields to enter the new address information
    $form['newaddress']['fieldset']['first_name'] = array(
      '#type' => 'textfield',
      '#title' => t('First Name:'),
      '#size' => 30,
      '#maxlength' => 128,
      '#required' => TRUE,
      '#default_value' => isset($edit_address) ? $edit_address->add_first_name : '',
    );
    
    $form['newaddress']['fieldset']['last_name'] = array(
      '#type' => 'textfield',
      '#title' => t('Last Name:'),
      '#size' => 30,
      '#maxlength' => 128,
      '#required' => TRUE,
      '#default_value' => isset($edit_address) ? $edit_address->add_last_name : '',
    );
    
    $form['newaddress']['fieldset']['streetaddress'] = array( 
      '#type' => 'textfield',
      '#title' => t('Address:'),
      '#size' => 30,
      '#maxlength' => 128,
      '#required' => TRUE,
      '#default_value' => isset($edit_address) ? $edit_address->add_address : '',
    );
    
    $form['newaddress']['fieldset']['city'] = array(
      '#type' => 'textfield',
      '#title' => t('City:'),
      '#size' => 30,
      '#maxlength' => 128,
      '#required' => TRUE,
      '#default_value' => isset($edit_address) ? $edit_address->add_city : '',
    );
    
    $form['newaddress']['fieldset']['country'] = array(  
      '#type' => 'select',
      '#title' => t('Country:'),
      '#options' => $countries,
      '#required' => TRUE,
      '#default_value' => isset($edit_address) ? $edit_address->add_country : 1,
    );
    
    $form['newaddress']['fieldset']['state'] = array(
      '#type' => 'select',
      '#title' => t('State:'),
      '#options' => $states,
      '#default_value' => isset($edit_address) ? $edit_address->add_state : 0,
      ),
    );  
    
    $form['newaddress']['fieldset']['zipcode'] = array(
      '#type' => 'textfield',
      '#title' => t('Zip Code:'),
      '#size' => 10,
      '#maxlength' => 128,
      '#required' => TRUE,
      '#default_value' => isset($edit_address) ? $edit_address->add_zip_code : '',
    );

    //Add the submit button to save the new address in the database and update the address list with the new address added
    $form['newaddress']['fieldset']['save'] = array(
      '#type' => 'submit',
      '#name' => 'Add New Address',
      '#value' => 'Save',
      '#ajax' => array(
        'callback' => 'NewAddress_Save_ajax_callback',
        'wrapper' => 'NewAddress_wrapper',
        'method' => 'replace',
      ),
      '#submit' => array('NewAddress_submit'),
      '#validate' => array('NewAddress_validate'),
    );
    
    //Add the cancel button to cancel the "Add New Address" form
    $form['newaddress']['fieldset']['cancel'] = array(
      '#type' => 'button',
      '#name' => 'New Address Cancel',
      '#value' => 'Cancel',
      '#ajax' => array(
        'callback' => 'NewAddress_Cancel_ajax_callback',
        'wrapper' => 'NewAddress_wrapper',
        'method' => 'replace',
      ),
      '#limit_validation_errors' => array(),
      '#submit' => array(),
    );
    
  }
  
  //Else, add the enter new address button
  else {
    //Add a button to enter new addresses
    $form['newaddress']['addnewaddress'] = array(
      '#type' => 'button',
      '#name' => 'Add New Address',
      '#value' => 'Add New Address',
      '#ajax' => array(
        'callback' => 'NewAddress_ajax_callback',
        'wrapper' => 'AddAddress_wrapper',
        'method' => 'replace',
      ),
      '#prefix' => '<div id="AddAddress_wrapper">',
      '#suffix' => '</div>',
      '#limit_validation_errors' => array(),
      '#submit' => array(),
    );
  }
  

Shabana Navas
snavas@acromedia.com
Software Developer
Acro Media
https://www.acromedia.com
1-800-818-4564
Skype: super.shaba

Jaypan’s picture

With this line of code:

'#default_value' => isset($user->data['NewAddress']) ? $user->data['NewAddress'] : $default_value,

If the $user object has the $user->data['NewAddress'] field set on it, the radios will always be set to that value no matter what. Try looking at the code in my first post again. You want to set priority as follows:

Submitted value -> pre-existing value -> default value.

So if a value has been submitted, that should be used, as the user may have overridden the default. Else if no value has been submitted, then all we have is the pre-existing value, so we should use that. And if no pre-existing value exists, then the default value should be used.

shabana.navas’s picture

I actually tried that recommendation after your post and it still didn't check the new address. I currently have the #default_value commented, as it isn't working either way.

Shabana Navas
snavas@acromedia.com
Software Developer
Acro Media
https://www.acromedia.com
1-800-818-4564
Skype: super.shaba

Jaypan’s picture

In that case, you haven't set #value or #default_value, so the first option will always be selected no matter what happens.

shabana.navas’s picture

As I mentioned before, I have it working now with that nasty little for-loop that I created:

//We have to set the #value of each address in the radio box to the default_value as well, otherwise, the new address won't get checked as the default
foreach($form['addresses']['radios']['#options'] as $key => $value) {
  $form['addresses']['radios'][$key]['#value'] = isset($user->data['NewAddress']) ? $user->data['NewAddress'] :  $default_value;
}

So, basically, the #default_value in my radios definition is really not needed. Would appreciate it, if you could let me know, if the reason for the initial default value getting set is the ajax button being a #type "submit". If that is the case, then what can I do to get around it, as I need my #submit handlers to be called when the ajax save button is clicked.

Shabana Navas
snavas@acromedia.com
Software Developer
Acro Media
https://www.acromedia.com
1-800-818-4564
Skype: super.shaba

zahari_bgr’s picture

I had the same problem with my code today.
I had to update a textarea element based on selectbox's value and #default_value didn't work, and #value was continuously breaking the user subbmition until I moved the code that sets #value to my AJAX callback.
Now my textarea element is updating properly and user input is properly delivered to my submit handler.

happysnowmantech’s picture

I found this thread after running into a similar problem when trying to use #default_value in form elements when using AJAX callbacks. Here is some example code for doing this:


/**
 * Example of how to set default values for form fields when using AJAX callbacks.
 * 
 * In this example, the form has a select field called "Size" and a text
 * field called "Price".  When the user selects a different value for
 * "Size", the value of "Price" is updated automatically via AJAX.
 */
function my_example_form($form, &$form_state) {
  $form['size'] = array(
    '#title' => t('Size'),
    '#type' => 'select',
    '#options' => array(
        0 => '--',
        1 => 'Small',
        2 => 'Medium',
        3 => 'Large'
     ),
    // #default_value only gets used the first time the form page is loaded,
    // NOT for any subsequent AJAX calls
    '#default_value' => 0,
    '#ajax' => array(
      'callback' => 'my_example_ajax_callback',
      'wrapper' => 'price-wrapper',
      'method' => 'replace',
      'effect' => 'fade',
    ),
  );
  $form['price'] = array(
    '#title' => t('Price'),
    '#type' => 'textfield',
    '#prefix' => '<div id="price-wrapper">',
    '#suffix' => '</div>',
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );

  return $form;
}

function my_example_ajax_callback($form, &$form_state) {
  if (!empty($form_state['values']['size'])) {
    $size = $form_state['values']['size'];
    if ($size == 1) {
      // You could also do something like:
      // $form['price']['#value'] = $form['price']['#default_value']
      // if your form builder function already computed/set a #default_value
      $form['price']['#value'] = '$1.99';
    }
    if ($size == 2) {
      $form['price']['#value'] = '$2.99';
    }
    if ($size == 3) {
      $form['price']['#value'] = '$3.99';
    }
  }
  else {
    $form['price']['#value'] = '';
  }

  return $form['price'];
}

function my_example_form_submit($form, &$form_state) {
  $size = $form_state['values']['size'];
  $price = $form_state['values']['price'];
  drupal_set_message(t('You submitted: Size = %size, Price = %price', array('%size' => $size, '%price' => $price)));
}

japo32’s picture

I realize this is an old comment but I'm updating it for posterity.

According to https://www.drupal.org/node/752056

Changes to the form must only be made in the form builder function, or validation will fail. The callback function must not alter the form or any other state.

In this case that's my_example_form()

Also since $form['price'] is fixed value, the default value is not really being used contrary to the description.

DrupalFrank’s picture

I'm also changing the default values of the form using ajax callback. (Trigger is user making a selection in a dropdown --- triggers different default values in radios etc of the form that they can then edit.) For maintainability reasons, I wanted to do all this server-side using the Drupal Form API rather than use custom jQuery/javascript tricks to change the radio button values after building the page.

Rebuilding the form from ajax automatically works fine for dropdown lists, custom markup, and text inputs. However, and frustratingly, the radio button and checkbox controls of the Form API behave differently; and that difference is really not well documented. I guess this is an edge case that not many developers run into.

Here is what worked for me...

  1. In the callback function set the $form_state['input'][$myradioelemname]=$newdefaultvalue
  2. In the callback function also set $form_state['ajax_values'][$myradioelemname]=$newdefaultvalue
  3. In the form builder function, check for existence of $form_state['ajax_values'][$myradioelemname] and if found create $formelement['#default_value'] attribute and assign the value from $form_state['ajax_values'][$myradioelemname]to it
  4. In the form builder function clear $form_state['ajax_values']

After implementing that, the behavior looks good for my needs.

anne.lev’s picture


Finally got this worked out


I have a select field in a form. When the select field is changed, a list of checkboxes is loaded. So, depending on selected value, I need to see checked elements. My solution is to change the state of checkboxes in AJAX callback. I added checkboxes (not checkbox) field in a form without default value. And in AJAX callback I changed checkboxes' '#checked' to TRUE, depending on selected value of a select list.

function test_form($form, &$form_state) {
  $form['select_list'] = array(
    '#type' => 'select',
    '#options' => $options,
    '#empty_option' => t('Empty option'),
    '#ajax' => array(
      'callback' => 'ajax_example_autocheckboxes_callback',
      'wrapper' => 'checkboxes-div',
      'method' => 'replace',
      'effect' => 'fade',
    ),
  );
  $form['checkboxes_fieldset'] = array(
    '#prefix' => '<div id="checkboxes-div">',
    '#suffix' => '</div>',
    '#type' => 'fieldset',
  );
  $form['checkboxes_fieldset']['checkboxes'] = array(
    '#type' => 'checkboxes',
    '#options' => $options,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  return $form;
}

function ajax_example_autocheckboxes_callback($form, $form_state) {
  //  To uncheck all checkboes, when select field is changed.
  //  Get a list of checkboes('#options') in a checkboxes group and set its '#checked' state to FALSE.
  foreach($form['checkboxes_fieldset']['checkboxes']['#options'] as $option) {
    $form['checkboxes_fieldset']['checkboxes'][$option]['#checked'] = FALSE;
  }
  
  if(!empty($form_state['values']['select_list'])) {
    $selected_checkboxes = '//an array of checked ones is loaded from DB';
    foreach($selected_checkboxes as $value) {
      if(array_key_exists($value, $form['checkboxes_fieldset']['checkboxes'])) {
        $form['checkboxes_fieldset']['checkboxes'][$value]['#checked'] = TRUE;
      }
    }
  }
  return $form['checkboxes_fieldset'];
}
javier.martin’s picture

Thanks, it works!!!!!!!

In Drupal 8 before to do this trick you have to call Checkboxes::processCheckboxes on ajax callback. More info: https://www.drupal.org/node/2758631

kpv’s picture

upd: The key is to check FormBuild::processForm() - there you will see that the form is build twice. At first always an empty $form_state array is used and user input mapped to the form elements. Then it calls other #process callbacks for the children elements. When done, the second cycle is performed with already ready $form_state.

So here we use #process callback to create a list of options, the list itself is keyed with the selected option key, so if the select was changed no value from user input will get mapped to the options list and the #default value will be used.

// instead of $form['checkboxes_fieldset'] use

$ajax_wrapper_id = 'some-wrapper-id';
$form['checkboxes_container'] = [
  '#prefix' => '<div id="some-wrapper-id">',
  '#suffix' => '</div>',
  '#process' => [[$this, 'processCallback']],
  '#type' => 'container',
];


// then in processCallback you will have access to the currently selected item and element #array_parents and #parents arrays
public function processCallback(array $element, FormStateInterface $form_state, $form) {
  $select_element_parents = array_slice($element['#parents'], 0, -1);
  $selected_item = $form_state->getValue(array_merge($select_element_parents, ['select_list']));

  // you may also check here if $seleted_item isn't empty

  // here build your list
  // since $selected_item is different for every select item, the default value of the list here
  // will not be overridden
  $form['checkboxes_container'][$selected_item]['checkboxes'] = [
    ...
    '#default_value' => 'val',
    ...
  ];
}


function ajax_callback(array $form, FormStateInterface $form_state, Request $request) {
  $triggering_element = $form_state->getTriggeringElement();
  $visualn_style_id = $form_state->getValue($form_state->getTriggeringElement()['#parents']);
  $triggering_element_parents = array_slice($triggering_element['#array_parents'], 0, -1);
  $element = NestedArray::getValue($form, $triggering_element_parents);

 
  return $element['checkboxes_container'];
}