I've been trying to figure this out for the past day or two: the correct way to pre-populate a Drupal 7 form's elements with arbitrary values once it has already been created and submitted once. During the submit handler, I have logic in there that decides whether to redisplay the form with new values (that I come up with, based on the user's input), or do something else. The scenario relevant to this post is redisplaying the form with new values, derived programmatically in the ..._submit() function, to pre-populate some of its fields.

Sounds easy, right? Seems to be straightforward and a pretty darn common scenario that must have a clear and easy solution. So you'd think, maybe, setting the appropriate $form_state['values'] array element would do the trick? Well, no, apparently that's not what $form_state['values'] is designed for, and sure enough, it doesn't work in practice. Okay, fine. That rules out form_set_value(), too. No problem - it does seem a bit unintuitive that form_set_value() doesn't actually set a value in the form that will be read the next time the form is created - but that's fine, let's move on.

So, let's just set the ['#value'] of the corresponding field in the $form (also passed to the ..._submit() function) to the value we're trying to set it to. This is based on advice by this Drupal expert at the end of his comment. Doesn't work. Maybe because $form is not passed to ..._submit() a reference? Okay, maybe he meant something else.

Finally, after much Googling, I come across this solution, thanks to the efforts of poor Alex, struggling with the same problem. Alex says you have to use the copy of the form provided by $form_state['complete form'] to set the ['#value'] property of the field you want to pre-populate. Doing this, as he says, in fact does allow the value to be pre-populated in my form the next time it happens.

So, if this is the correct way to do this (or is it? can anyone confirm?), then why isn't it mentioned anywhere in the Drupal 7 API? It seems like such a basic and essential part of form manipulation.

My hope is that I've missed the obvious, and that the FAPI documentation in fact does clearly explain the correct procedure for modifying the form state programmatically.

Comments

duckzland’s picture

I think better use $form_state['storage']['somefield'] and use some logic in $form['somefield']['#default_value'] to switch between $form_state['storage'] if exists or use other value taken from database if exists.

--------------------------------------------------------------------------------------------------------
if you can use drupal why use others?
VicTheme.com

paul2’s picture

Changing $form['element']['#default_value'] in the ..._submit() handler doesn't seem to have an effect anymore than changing $form['element']['#value'] does. But are you suggesting not trying to change the form in the ..._submit() handler at all, and rather do so in the form builder function? Is this the correct approach?

The only way I could do that would be to run the form builder a second time, after the submit handler is run, by setting $form_state['rebuild'] = TRUE at the end of ..._submit(). Currently, upon submission, I have the form being rebuilt once, then the submit handler runs, and then the form is fed to the browser. It would be nice to keep the number of form rebuilds to one, as I have now.

duckzland’s picture

is this for a "field widget" or normal form?

anyway for a normal form :

you dont change the default_value in the submit handler, instead you do the logic for changing from a default value taken from database or value from form_state in the form array it self.

the submit handler only move the value from $form_state['values'] to $form_state['storage'] so when the form is rebuild / reloaded / refreshed the form array can grab the storage and decide to use the stored value or value from database.

as for field widget, this approach need further modification, the concept is to build the form state of the widget and when processed use it to set the parent form form_state.

--------------------------------------------------------------------------------------------------------
if you can use drupal why use others?
VicTheme.com

paul2’s picture

is this for a "field widget" or normal form?

This is just for a normal, simple form with regular text fields.

you dont change the default_value in the submit handler, instead you do the logic for changing from a default value taken from database or value from form_state in the form array it self.

Well that makes sense to me, and I thought I tried that already. Okay, so I changed my code to store the new default data in $form_state['storage'] in my submit function, then in my form building function, I check to see if any values are in storage. If they are, I set the ['#default_value'] of the fields I want to fill with those values.

Sadly, that doesn't work. I made sure to set $form_state['rebuild'] = TRUE to make sure the form builder function would be called again, and it is, and the values are properly being retrieved from storage and being assigned to ['#default_value']. I suspect Drupal is ignoring the ['#default_value'] setting, however, because the form has already been created. That's how ['#default_value'] is supposed to work, right? It's only supposed to provide a default value for the element when the form is first created, not after it has already been submitted. However, this form has already been submitted, perhaps multiple times, so I guess ['#default_value'] is not an appropriate solution here.

Any other ideas? This is why I've become so frustrated (as you might have sensed :). I'm going to have to resort back to setting the value in $form_state['complete form'] again for now.

duckzland’s picture

I assume you do have &$form_state in the submit and form function?

like :


function somefunctio_form(&$form, &$form_state) {
}

the values are properly being retrieved from storage and being assigned to ['#default_value']. I suspect Drupal is ignoring the ['#default_value'] setting

if the form has $form_state['values'] then I believe ['#default_value'] is ignored and use ['#value'] instead

--------------------------------------------------------------------------------------------------------
if you can use drupal why use others?
VicTheme.com

aLearner’s picture

paul2 et. al,

I wonder if I'm struggling with the exact problem here

http://drupal.org/node/1397100

I've been dealing with this for a few days now with absolutely no luck. I just can't seem to find a solution. Also, regarding default values, I noticed in the FAPI that its definition is

The value of the form element that will be displayed or selected initially if the form has not been submitted yet.

If the form was already submitted once setting 'default_value' does not work:

// This does NOT work if the form was already submitted once
'#default_value' => $someValue,

Thanks

paul2’s picture

My form builder and submit handler functions are defined exactly as specified in the Drupal 7 API for form generation, but that means my $form argument is not passed by reference.

if the form has $form_state['values'] then I believe ['#default_value'] is ignored and use ['#value'] instead

Okay. Maybe that's what I'm already doing in the submit handler by setting the ['#value'] property in the $form_state['complete form'] reference. Perhaps it's safer if I set the ['#value'] in the form generation function instead.

aLearner’s picture

Hi paul2,

Just wondering what you mean by

I'm going to have to resort back to setting the value in $form_state['complete form'] again for now.

I'm not sure I understand how I can use this to pre-populate / retrieve (the values of various fields) for a previously submitted form. Any help / guidance would be greatly appreciated!

Thank you!

paul2’s picture

Hi Vinit,

Here's a more complete example of my solution. Here's the form builder function:

function mymodule_form($form, &$form_state) {
  ...
    $form['post_fieldset']['post_id'] = array(
      '#type' => 'hidden',
    );
    $form['post_fieldset']['post_title'] = array(
      '#type' => 'textfield',
      '#title' => t('Title'),
      '#required' => TRUE,
    );
    $form['post_fieldset']['post_content'] = array(
      '#type' => 'textarea',
      '#title' => t('Content'),
      '#required' => TRUE,
    );
  ...
  return $form;
}

The submit handler:

function mymodule_submit($form, &$form_state) {
  $my_value = $form_state['values']['some_submitted_value'];
  $my_value2 = 'some value from arbitrary logic';

  // Prefill form.
  $form_state['complete form']['post_fieldset']['post_id']['#value'] = $my_value;
  $form_state['complete form']['post_fieldset']['post_title']['#value'] = $my_value2;
  $form_state['complete form']['post_fieldset']['post_content']['#value'] = 'my arbitrary value';
}
aLearner’s picture

paul2,

Thanks for the clarification and the example. This really helps things for me.

I had another (hopefully related) question:

So now that we have this

function mymodule_submit($form, &$form_state) {
  $my_value = $form_state['values']['some_submitted_value'];
  $my_value2 = 'some value from arbitrary logic';

  // Prefill form.
  $form_state['complete form']['post_fieldset']['post_id']['#value'] = $my_value;
  $form_state['complete form']['post_fieldset']['post_title']['#value'] = $my_value2;
  $form_state['complete form']['post_fieldset']['post_content']['#value'] = 'my arbitrary value';
}

Is it possible to have another function that'll give the user the option to edit the same form she submitted previously (in the above function). If so, how do we pre-populate the fields with '$my_value', '$my_value2' and 'my arbitrary value'? I tried the following piece of code but it does not work.

function mymodule_form_edit($form, &$form_state) {
  ...
    $form['post_fieldset']['post_id'] = array(
      // Attempt to prepopulate the field with what the user entered the first time
      '#default_value' => $my_value2,
      '#type' => 'hidden',
    );
    $form['post_fieldset']['post_title'] = array(
      '#type' => 'textfield',
      '#title' => t('Title'),
      // Attempt to prepopulate the field with the arbitrary logic
      '#default_value' => $my_value2,
      '#required' => TRUE,
    );
    $form['post_fieldset']['post_content'] = array(
      '#type' => 'textarea',
      '#title' => t('Content'),
      // Attempt to prepopulate the field with the arbitrary value string
      '#default_value' => 'my arbitrary value',
      '#required' => TRUE,
    );
  ...
  return $form;
}

I've tried drupal_set_messaging all three values ('$my_value', '$my_value2', 'my arbitrary value') and they print out just fine. However, I can't get them to be set in the form once it's already been submitted and I can't figure out what I'm doing wrong.

Also, sorry if this is not in line with original question. I'm really hoping it is.

Thanks very much,

paul2’s picture

Is it possible to have another function that'll give the user the option to edit the same form she submitted previously (in the above function)

Yup. So let me just make sure I understand: you have values in storage, saved from some past point of the form's existence, and you want to prefill the form with those values on demand (rather than in the submit handler, like I need to do). So if you follow the conversation I've been having with @duckzland above, you'll see that I encountered the exact same problem with using '#default_value' in the form building function as you have. It doesn't work. So he responded explaining that you should be using the '#value' property instead. Try that out.

aLearner’s picture

Yup. So let me just make sure I understand: you have values in storage, saved from some past point of the form's existence, and you want to prefill the form with those values on demand (rather than in the submit handler, like I need to do).

Yes, you got that right exactly.

So if you follow the conversation I've been having with @duckzland above, you'll see that I encountered the exact same problem with using '#default_value' in the form building function as you have. It doesn't work. So he responded explaining that you should be using the '#value' property instead. Try that out.

OK. Thank you for bringing this to my attention. This does seem to work perfectly; but not always. When I use the 'text_format' attribute, I'm unable to get the text area to show up - let alone set the value. Consider the following function

// Function to edit a previously submitted form

function mymodule_form_edit($form, &$form_state) {
  ...
     $form['post_fieldset']['post_content'] = array(
      // Try to use a text-format-enabled version of a textarea - but this fails.
      // However, using 'textarea' instead of 'text_format' works fine.
      '#type' => 'text_format',
      '#rows' => '10',
      '#cols' => '60',
      '#title' => t('Body'),
      // Attempt to prepopulate the field with the value stored in '$someText'
      '#value' => $someText,
      // CKEditor on my installation of Drupal is set so that it works in the 'plain_text' mode
      '#format' => 'plain_text',
      '#required' => TRUE,
    );
   
   // This ALWAYS works fine and prints out the content stored in '$someText'
    drupal_set_message('Body should show up here: ' . $someText, 'status', FALSE);
  ...
  return $form;
}

I'm wondering if this is a related problem or a new issue altogether because the text area doesn't show up in the 'edit' version of the function: 'mymodule_form_edit'. It shows up just fine in the function 'mymodule_form' (which is used to fill up the form the first time):

// Function to fill up a form

function mymodule_form($form, &$form_state) {
  ...
     $form['post_fieldset']['post_content'] = array(
      // Try to use a text-format-enabled version of a textarea - this works!
      '#type' => 'text_format',
      '#rows' => '10',
      '#cols' => '60',
      '#title' => t('Body'),
      // Make sure that the field is blank initially
      '#default_value' => '',
      // CKEditor on my installation of Drupal is set so that it works in the 'plain_text' mode
      '#format' => 'plain_text',
      '#required' => TRUE,
    );
  ...
  return $form;
}

Any thoughts? I can't figure it out. The only thing that's changed is setting '#default_value' to '#value'.

Thanks very much for your help.

P.S:

[1] It looks similar to what 'hass' says in the beginning of this post and comment # 1

http://drupal.org/node/820816

[2] It's also similar to this thread in that the text area or the WYSIWYG editor don't load at all

http://drupal.org/node/739558

paul2’s picture

I've also been climbing the learning curve as I try to implement a '#text_format' type of input element into my own form. It seems that this element type is a "special type", and unfortunately terribly documented. This element type actually stores two values: the text input by the user, and the format chosen by the user. To accommodate these two values, when the form is processed, all '#text_format' elements have two sub-elements created for it: 'value' and 'format'. I just discovered this by myself using error_log(print_r(..., true)) statements in my submit handler (which you might want to be doing as well to inspect form structures, or perhaps you have another debugging solution).

The text value of the field is can be found under $form_state['values']['some_text_format_field']['value'] element. Keep in mind that with any '#text_format' element you should be just as interested in saving the user's chosen input format, which I think can be found in $form_state['values']['some_field']['format'].

In the case of your setting ['#value'] in the form builder, I'm not sure what the correct solution is. You need to be able to assign the ['#value'] element of the 'value' sub-element of the formatted text field which (I think) hasn't even been generated yet in your form builder function. Those sub-elements are probably created later by some other function.

Perhaps you can follow the technique I'm using of changing values in the 'complete form' form reference, accessed from the $form_state array? That might work, but you may have to do this outside of your form builder function.

Good luck! I've got to focus on my own list of problems now, I hope you find a solution.

Matthew Davidson’s picture

This must be the worst DX WTF I've encountered in the last couple of years. The solution I've found that seems least offensive to me is to alter the values in $form_state['input'] and force the form to not cache:

/**
 * Implements hook_form_FORM_ID_alter().
 */
function mymodule_form_some_form_alter(&$form, &$form_state, $form_id) {
  $form['mymodule_prepopulate_button'] = array(
    '#type' => 'submit',
    '#value' => t('Prepopulate'),
    '#submit' => array('_mymodule_form_some_form_prepopulate'),
  );
}

/**
 * Submit callback. Prepopulates a form value.
 */
function _mymodule_form_some_form_prepopulate($form, &$form_state) {
  $form_state['input']['field_some_field'][LANGUAGE_NONE][0]['value'] = 'An arbitrary value.';
  $form_state['rebuild'] = TRUE; // Not even sure this is necessary.
  $form_state['no_cache'] = TRUE;
}

I can't help thinking that the Right Way to do this would be to alter the '#default_value' of an element in the form alter implementation, based on the value(s) of some variable(s) you tuck away in $form_state in the submit callback. This is how the node form preview button works, and anything else seems dreadfully kludgy. But although this works for adding new elements to the form, I can't find a way to make it work for elements that have already had their values set.

Jaypan’s picture

I use the following method:

1) Query any data from the database. Create an object with this data
2) Call drupal_get_form() and pass it the form_definition function name, and the object you created (ex: drupal_get_form('some_form', $some_object))
3) Use this object in your form definition to set your default values
4) On submit, save the data to the database. If you have no redirects, the page will be refreshed, and the steps will be repeated from step 1.

An example I wrote with some code: http://drupal.org/node/1670656#comment-6198558

petercook’s picture

I must admit I wonder if I will ever get my head around the workings of drupal.

flaviovs’s picture

I managed to workaround this issue by doing:

function my_form($form, &$form_state)
{
     if ( isset($form_state['storage']) )
     {
	  $field_a_default = $form_state['storage']['field_a_default_set_in_submit_handler'];
	  unset($form_state['input']['field_a']);
     }
     else
     {
	  $field_a_default = 'foo bar' ;
     }
     //(...)
     $form['field_a'] = array(
	  '#type' => 'textfield',
	  '#default_value' => $field_a_default,
	  // (...)
	  );
     return $form;
}