It seems that using form_set_value() from within an #element_validate callback has no effect.
If this is by design, I guess it should be added to documentation.

Comments

jhodgdon’s picture

Category: feature » bug
Status: Active » Postponed (maintainer needs more info)

Hm. I think that this is supposed to work. Can you provide more information?

Also note that the documentation for form_set_value() says:

 * Use this function to change the submitted value of a form element in a form
 * validation function, so that the changed value persists in $form_state
 * through the remaining validation and submission handlers. It does not change
 * the value in $element['#value'], only in $form_state['values'], which is
 * where submitted values are always stored.
anrikun’s picture

The documentation says this too:

Note that form validation functions are specified in the '#validate' component of the form array (the value of $form['#validate'] is an array of validation function names). If the form does not originate in your module, you can implement hook_form_FORM_ID_alter() to add a validation function to $form['#validate'].

So I guess form_set_value() only works from within a #validate callback but not an #element_validate callback?
It is not clearly stated.

Anyway, I may be misusing the function too so I will check my code again and let you know.

jhodgdon’s picture

It should work within either type of validate callback, as far as I know. So... some code that isn't working would be helpful to see.

anrikun’s picture

Here is some code. I have simplified it so it's just an example:

<?php
/**
 * Implements hook_form_FORM_ID_alter(); alter myform.
 */
function mymodule_form_myform_alter(&$form, &$form_state) {
  $form['qty']['#element_validate'][] = '_mymodule_qty_validate';
}

function _mymodule_qty_validate($element, &$form_state, $form) {
  if (intval($element['#value']) > 1) {
    form_error($element, t('You can only have 1 copy of this item in your cart. Qty has been corrected.'));
    form_set_value($element, 1, $form_state);
  }
}
?>

What I expect if user submits a value over 1 for 'qty':
- An error is displayed.
- 'qty' field displays 1.

What I get:
- An error is displayed.
- 'qty' field still displays submitted erroneous value.

I guess this is by design. Does it mean that $form_state['rebuild'] = TRUE; is needed here?

jhodgdon’s picture

Component: documentation » forms system
Status: Postponed (maintainer needs more info) » Active

Well, I don't know what to tell you for sure. I looked at
drupal_validate_form()
and there is some code in there about '#limit_validation_errors' -- it looks like if this is set (not sure how/when it does get set), values set during validation on elements that failed validation would be cleared. Is it possible that is what is happening in your case?

It looks like that might be set up in
_form_validate()

That's about all I can gather... I don't know if this is a support request or a code bug at this point, so I'm moving it to the forms component (I don't think it's a documentation bug). You might try getting some help in IRC (see http://drupal.org/irc) so you can narrow it down... for now I am out of ideas and I'm un-following this issue. Sorry I didn't have more to offer you.

anrikun’s picture

Category: bug » support

@jhodgdon: thanks for your help anyway, much appreciated :-)

j0rd’s picture

@anrikun I'm having similar issue, but mine is working in everything except IE9.

If you use $form_state['rebuild'] = TRUE, which is required for my form to work, this breaks other ajax stuff, like with an image field. Attempt to add an image field to your node_form and then see if you can "update image" in IE9. For me it doesn't work.

This is the last remaining problem I have with my beast of a widget on a node form.

rszrama’s picture

Status: Active » Closed (works as designed)

form_set_value() within an #element_validate callback does work. This code example comes from the Commerce Product Reference autocomplete textfield widget:

function commerce_product_reference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
    // ..
    return $element + array(
      '#type' => 'textfield',
      '#default_value' => implode(', ', $skus),
      '#autocomplete_path' => $instance['widget']['settings']['autocomplete_path'] . '/' . $instance['entity_type'] . '/' . $field['field_name'] . '/' . $instance['bundle'],
      '#size' => $instance['widget']['settings']['size'],
      '#maxlength' => 2048,
      '#element_validate' => array('commerce_product_reference_autocomplete_validate'),
    );
  }
}

/**
 * Validation callback for a commerce_product_reference autocomplete element.
 */
function commerce_product_reference_autocomplete_validate($element, &$form_state, $form) {
  // If a value was entered into the autocomplete...
  if (!empty($element['#value'])) {
    // Translate SKUs into product IDs.
    $typed_skus = drupal_explode_tags($element['#value']);

    $value = array();

    // Loop through all the entered SKUs...
    foreach ($typed_skus as $typed_sku) {
      // To see if the product actually exists...
      if ($product = commerce_product_load_by_sku(trim($typed_sku))) {
        // And store its product ID for later validation.
        $value[] = array('product_id' => $product->product_id);
      }
      else {
        form_error($element, t('Product SKU %sku does not exist.', array('%sku' => $typed_sku)));
      }
    }
  }
  else {
    $value = array();
  }

  // Update the value of this element so the field can validate the product IDs.
  form_set_value($element, $value, $form_state);
}

It could be you're just using in invalid $value for the form element. However, I'm going to close out this request, as in your case you simply shouldn't make the value editable if it can only ever be 1. ; )

j0rd’s picture

For me, it works just fine in all browsers but IE9.

And the issue is not that my form_set_value() code in #element_validate doesn't work, but it breaks an image upload.

Again only in IE9.

I have no idea how to fix it...but currently IE9 users on my site are not able to upload images on their nodes because of this.

It's a very strange bug, and most likely due to some bad JS in core or modules.

If I do remove my #element_validate hook on the form though, the image upload works fine in IE9.

I am using jquery_update (jQuery 1.7), but I have also tried disabling that to see if it works, and it doesn't help.

rszrama’s picture

Hmm, and there's no resulting error message from that form validation? When an AJAX process is triggered, it's validating the whole form before rebuilding just that part of the form that would include the uploaded image. But the validation all happens server side, not client side in some JS that should screw up depending on whether or not it had validation.

jigarius’s picture

Issue summary: View changes

Facing the same problem! I am using form_set_value() within an #element_validate callback, but it does not seem to work. Spent over 2 hours now.

nvahalik’s picture

Just putting some notes in here in case anyone else comes across this.

I had a use case where I was getting a custom line item field value in the add to cart form (a custom price field for a gift card) and people were putting all sorts of funky-formatted values in the field. The field is already set up and I didn't want to have to change the field type or make a bunch of big changes so I figured I'd just do some pre-validation, parse and clean the value, and then let the rest of the validation system do it's job. However, it was not working.

As Ryan mentioned, calling form_set_value() from within an #element_validate callback does work in that it properly updates the form. Validation functions set at the form level will see the correct value. However, if you're wanting to do something like perform a custom validation on a value—cleaning it up and parsing it, for instance— before a subsequent validator gets it then form_set_value() will not work because those element validator functions do not look at $form_state to get their values. Instead, they are looking at the $element array.

As a workaround, you could replace that validator with your own custom function, make your changes, and then just call the original validator at the end of your custom function. This is the solution that I chose to do. Example:

function my_module_form_alter($form, &$form_state) {
    $form['line_item_fields']['field_amount']['und']['#element_validate'] = array('check_parse_amount'); // <-- removes the original 'select_or_other
}

function check_parse_amount($element, &$form_state) {
  if (!empty($form_state['values']['line_item_fields']['field_amount']['und']['other'])) {
    $value = $form_state['input']['line_item_fields']['field_amount']['und']['other'];
    $cleaned_value = value_parse_clean($value);
    form_set_value($element, array('other' => $cleaned_value, 'select' => 'select_or_other'), $form_state);
    $element['other']['#value'] = $cleaned_value; // <-- this set the value on the element.
  }
  return select_or_other_field_widget_validate($element, $form_state); // <-- pass it through!
}
Fabianx’s picture

Just to add to this:

If you need to have your new form_state value be used in your form, then you need to also change $element and pass that by reference.

e.g.

<?php
/**
 * Implements hook_form_FORM_ID_alter(); alter myform.
 */
function mymodule_form_myform_alter(&$form, &$form_state) {
  $form['qty']['#element_validate'][] = '_mymodule_qty_validate';
}

function _mymodule_qty_validate(&$element, &$form_state, $form) {
  if (intval($element['#value']) > 1) {
    form_error($element, t('You can only have 1 copy of this item in your cart. Qty has been corrected.'));
    form_set_value($element, 1, $form_state);
    $element['#value'] = 1;
  }
}
?>