I would like to know the difference of two ways to change a form field value on form validation.

First one is changing directly the

$form_state['values']

Second one is with drupal api function:

form_set_value($form_item, $value, &$form_state)

I'm using now first but the second seems more correct and I don't know the reason.

This is an example:

First:

  $form['one']['two']['item'] = array(
    '#type' => 'textfield',
    '#title' => t('My Item'),
    '#description' => t('My cool item.'),
    '#required' => TRUE,
    '#default_value' => 'cool',
    '#maxlength' => 20,
  );
...
  //on validation
  $form_state['values']['item'] = 'newvalue';

Second:

  $form['one']['two']['item'] = array(
    '#type' => 'textfield',
    '#title' => t('My Item'),
    '#description' => t('My cool item.'),
    '#required' => TRUE,
    '#default_value' => 'cool',
    '#maxlength' => 20,
  );
...
  //on validation
  form_set_value($form['one']['two']['item'], 'newvalue', $form_state);

I would like to know the difference of one approach and the other one.

Comments

I'd like to know this too.

The doc comment on the function is quite informative

Use this function to change the submitted value of a form item in the
validation phase so that it persists in $form_state through to the
submission handlers in the submission phase.

Sean Burlington
www.practicalweb.co.uk

id be interesed in something similar
im looking to change the value of a field on validation, and if validation fails have the changes persist on the rerendered form,

say for instance, someone submits a phone number, the form may not validate but will correct the phone number to the correct format

I'm also interested in this issue. I would like, when a form is validated, to be able to change the value of a form element, so that if the form fails, that change persists to the rerendered form. That is, when a user submits the form, I would like to be able to change the value of form element A. However, let's assume that form element B is incorrect, and the form is rerendered. I would like the value I changed on element A to remain changed. Is there a way to do that?

I've tried using form_set_value() to do this, but the change does not persist.

When validation fails the submitted values are part of the $form_state['values'] array. You can check to see if the value is set in the array, and use it accordingly.

form_set_value() is only used to pass values to the submit function.

Jaypan
Our newest Drupal site: PacificAikido.com (Drupal showcase)

You mean, when the form is rebuilt? The form element I am attempting to change is created by hook_form_alter. So, are you suggesting that I add a check in hook_form_alter to see if there is a value set in the $form_state['values'] array and then set the #value to that?

Essentially yes. You can check if there is a value set in $form_state['values']. What you do with that value is up to you - which could include setting #value to that. Note that in setting #value, the user is not able to change/override this. It doesn't matter what they submit, Drupal will use the value set in #value. If you want it to be overridable, you can set it in #default_value.

Jaypan
Our newest Drupal site: PacificAikido.com (Drupal showcase)

I really appreciate your help with this. Since the form element is being added by another module, I hesitate to change that module's code, so I've tried two different additions to the validate handler in my module:

$form['my_element']['#default_value'] = $updated_value;

$form['my_element']['#value'] = $updated_value;

Neither of these worked. I've used the Devel module to print out the $form array at the end of the validation handler, and it looks right, but when the form is rebuilt after validation failure, the value is not changed. I suppose something else intervenes to override my setting.

I'm going to add a hook_form_alter for the third-party module's form, to set the default value to see if that works.

That little bit of code with no context is not enough for me to tell you anything.

Jaypan
Our newest Drupal site: PacificAikido.com (Drupal showcase)

Sorry, I'll be a bit more explicit. I have the Workflow module installed, and I'm wanting to add an extra submit button "Save Draft" that will change the value of the Workflow form element to the "Draft" status. So, what I just tried was to create a module that adds the submit button, and then a validate handler that checks to see which submit button was clicked:

function draft_project_validate(&$form, &$form_state) {
  // set the node to be draft or review depending on which button was clicked
  if ($form_state['triggering_element']['#value'] == $form['actions']['submit_draft']['#value']) {
    form_set_value($form['workflow']['Project Status'],'2',$form_state);
    $form['workflow']['Project Status']['#value']='2';
    //I also tried $form['workflow']['Project Status']['#default_value']=='2';
  } else if {
    form_set_value($form['workflow']['Project Status'],'3',$form_state);
    $form['workflow']['Project Status']['#value']='3';
    //I also tried $form['workflow']['Project Status']['#default_value']=='3';
  }
}

This successfully sets the value when getting passed to the submit handler, but if the validation fails, the Workflow form element remains the same as whatever was set directly on the form. If I click the "Draft" radio button on the Workflow form element, but then click "Submit", a failed form is rebuilt and the "Draft" radio button is still selected, rather than the "Review" as expected. If I click the "Review" radio button on the Workflow form element, but then click "Save as Draft", a failed form is rebuilt and the "Review" radio button is still selected, rather than the "Draft" as expected.

Does that help?

Yes and no. I don't know how the module you are showing works, but I get a better idea of what you are trying to affect from what you were speaking. I didn't realize you wanted to go off the button that was submitted, I thought it was an entered value.

Anyways, to do a test, I wrote the following function (module name: test):

<?php
function test_form_alter(&$form, &$form_state, $form_id)
{
  if(
$form_id == 'user_login' && isset($form_state['input'], $form_state['input']['op']))
  {
   
$form['actions']['submit']['#value'] = t('Dude, sign in already');
  }
}
?>

On failed validation, the text of the login button is changed from 'Log in' to 'Dude, sign in already'.

Actually, it turns out I was incorrect, $form_state['values'] doesn't appear to be set when validation fails in D7. I'm pretty sure it is in D6, though I'd have to check again to be sure. Anyways, I did a debug() dump of the $form_state array in my hook_form_alter() to compare the values when the form is initially visited, and another one after validation fails, and I found that after validation fails $form_state['input']['op'] is set, when it previously wasn't. Now, I didn't test this beyond one single failed validation. I didn't test after a successful submission or anything, so you should definitely hard test this a little further before using the code I have above. But it should give an idea on how you can change the form values in hook_form_alter() after a failed validation, which hopefully will help you solve your problem.

Jaypan
Our newest Drupal site: PacificAikido.com (Drupal showcase)

Interesting...based on tests I've been doing, I had been convinced that hook_form_alter wasn't being called after failed validation, but your tests seem to show that they do (at least on the first failed validation). Here's the entire module, just in case you can see where I'm going wrong. I ended up trying to create an after_build function for the form after thinking that hook_form_alter wasn't being called after validation.

function draft_project_form_project_node_form_alter (&$form,&$form_state,$form_id) {
  if ($form_id == 'project_node_form') {
    $form['actions']['submit_draft'] = array(
      '#type' => 'submit',
      '#value' => t('Save as Draft'),
      '#class' => 'form-submit',
      '#weight' => '9',
      '#submit' => array('draft_project_project_node_form_submit'),
    );
    $form['#after_build'][] = 'draft_project_after_build_handler';
    if (empty($form['#validate'])) {
      $form['#validate'] = array();
    }
    array_unshift($form['#validate'], 'draft_project_validate');
  }
}
function draft_project_after_build_handler($form,&$form_state) {
  if (isset($form_state['values']['workflow'])) {
      $form['workflow']['Project Status']['#value']=$form_state['values']['workflow'];
    }
  return $form;
}
function draft_project_validate(&$form, &$form_state) {
  // Set the node to be draft or review depending on which button was
  // clicked.
  if ($form_state['triggering_element']['#value'] == $form['actions']['submit_draft']['#value']) {
    form_set_value($form['workflow']['Project Status'],'2',$form_state);
    $form['workflow']['Project Status']['#value']='2';
  }
  elseif ($form_state['triggering_element']['#value'] == $form['actions']['submit']['#value']) {
    form_set_value($form['workflow']['Project Status'],'3',$form_state);
    $form['workflow']['Project Status']['#value']='3';
}
function draft_project_project_node_form_submit ($form, &$form_state) {
  node_form_submit($form, $form_state);
}

You aren't doing any tests whatsoever on the $form_state element in your hook_form_alter().

Jaypan
Our newest Drupal site: PacificAikido.com (Drupal showcase)

Right, I moved that to the afterbuild function. I had tried putting it in hook_form_alter, but nothing ever happened after failed validation. When I moved it to the after build function, I could see that $form_state was changed, but the form didn't reflect the value in the $form_state. BTW, I'm not sure what I'm doing wrong, but I made a test module that I thought was identical to yours, but when I try to make the user login fail, the submit button does not change. This is Drupal 7, as you had suspected, if that makes a difference.

Add this to your hook_form_alter() function, and do some tests of the initial state vs. the failed validation state:

<?php
debug
($form_state, '$form_state', TRUE);
?>

Jaypan
Our newest Drupal site: PacificAikido.com (Drupal showcase)

I've tried to figure out how to make that work. I've altered my settings.php file to show errors, but to no avail. I've been using the Devel module to test the initial state of the form_state vs the failed validation state, though. I've added dpm($form_state) to hook_form_alter, but it only displays $form_state when the form is first built. After a failed validation the dpm() call is not fired (which is why I'm not sure that my hook_form_alter is getting called after form validation), and it doesn't show any information. I've tried putting dpm($form_state) in the validate handler, and that shows me the state of $form_state right after the validation script is done, but that doesn't show me the state of $form_state when it's getting built.

All indications are that my hook_form_alter is not being run after a failed validation, and neither is the one in the test module. I can't quite figure out why that might be, though. Again, I appreciate your working with me on this.

Try switch to debug(). I have no idea why dpm() is not fired each time, but I'm using it on D7 with all caches turned on, and debug() is fired every time, in hook_form_alter().

Are you using any other caching modules - boost maybe?

Jaypan
Our newest Drupal site: PacificAikido.com (Drupal showcase)

Yeah, I can't figure out why it might be that dpm() isn't getting fired each time, but I was able to finally find a solution. In my after_build handler, I made sure to change not only the value of ['workflow'] but each of the option elements in ['workflow']. Essentially, if I wanted to be certain that the value of that radio button group was changed, I needed to provide each radio button element with the same value, so that it knew to be turned on or off. I don't think I ever mentioned that it was a radio button select, which might have helped, but I didn't even consider that that might be necessary.

I may not have needed to put it in after_build, and I'm going to try again later with the radio button updates in the validate handler, to see if I can get it all to change there. I don't have access to the code right now, but I wanted to let you know that I found a solution and that I'm not using any other caching modules. Thanks for the assistance in walking through the problem.