3

I'm building a multistep node add form where you upload a zip of files on page 1, let the module read file names into an array when the uploader presses the "next" button, and then allows the uploader to enter some meta info for each file on page 2. I have set up the field collection "field_files" to hold info for each file.

However, I can't seem to figure out how to generate the field collection form code for page 2. I would like it to be pre-populated with one field collection entry for each file. I've been reading module source code and huge var_dumps all day without getting anywhere. Does anyone know if this is even possible, and if it is, what would be the best way to do it?

3
  • To clarify, you want help with the code required to generate the form & prepopulate some fields? & you already have a handle on the file parsing/info collection? I'm not sure exactly how far along you are in the workflow you've described. Oh, anything is possible if you have enough time ;)
    – Merrick
    Commented May 9, 2012 at 1:26
  • That's correct. I've solved the multistep form and file parsing.The only thing I have left is to generate the form on page two. I would like it to start with one pre-populated "field collection" field for each file, instead of just one blank field.
    – Erlend
    Commented May 9, 2012 at 1:33
  • I also haven't been able to figure out whether I need to pre-insert every field collection entry into the database before adding them to the form. Or are the field collection entries also saved on node submit?
    – Erlend
    Commented May 9, 2012 at 1:37

3 Answers 3

7

After hours of debugging I found a good working solution to pre populate fields in field collections. (see Drupal forum)

So whats the plan?

  1. Manually generate a node-add form for the desired bundle.
  2. Change the form state of the generated form and set the desired number of fields you want to pre-populate.
  3. Rebuild the node-add form with the new form state values.
  4. Use hook_form_alter() to pre-populate values.

Ok. So lets assume we have a bundle called bundle_with_unlimited_fc which belongs to the node entity type. Furthermore all our custom code will reside in our module named my_module.

We can use hook_menu_alter() to define a new page callback. Everytime a new bundle_with_unlimited_fc is added Drupal will call now this function.

function my_module_menu_alter(&$items) {
  $items['node/add/bundle_with_unlimited_fc']['page callback'] = 'my_module_build_form';
}

Now definde our form builder function. Notice the function argument which comes from the Drupal implementation of our altered menu item.

function my_module_build_form($type) {
  // get the standard node form;
  // we cannot get it from drupal_get_form() because we need the form_state reference,
  // so we copy a bit of code from node_add() and drupal_get_form()
  global $user;
  $form_id = 'bundle_with_unlimited_fc_node_form';
  $form_state = array();
  $form_state['build_info']['args'][] = (object) array(
    'uid' => $user->uid,
    'name' => (isset($user->name) ? $user->name : ''),
    'type' => $type,
    'language' => LANGUAGE_NONE
  );
  $form = drupal_build_form($form_id, $form_state);

  // we now have exactly the form which node_add() is returning BUT
  // we also have a reference of the form state array ready to alter;

  // how many items do we want?
  $num_items = 5;
  // this is exactly what the ajax request for the add more button does
  $form_state['field']['--machine-name-of-field--'][LANGUAGE_NONE]['items_count'] = $num_items;

  // now this is important!! set a flag inside the form state so that the hook_form_alter() function
  // is able to know when it should pre-populate (remember: hook_form_alter() will be called twice
  // during this process)
  $form_state['my_module_prepopulate'] = TRUE;

  // reset the html ids so that our new rebuilted form will get nice ids and not form_id--2
  drupal_static_reset('drupal_html_id');

  // rebuild the form and return it;
  // through drupal_rebuild_form() hook_form_alter() will be called again - this time with our
  // prepopulate flag and 5 empty form elements ready to prefill
  return drupal_rebuild_form($form_state['build_info']['form_id'], $form_state, $form);
}

Thats nearly all there is. Just setup your hook_form_alter() and you will be ready.

// hook_form_FORM-ID_alter().
function my_module_form_bundle_with_unlimited_fc_node_form_alter(&$form, &$form_state, $form_id) {
  if (isset($form_state['my_module_prepopulate']) && $form_state['my_module_prepopulate']) {
    $delta = 0;
    $max_delta = $form['--machine-name-of-field--'][LANGUAGE_NONE]['#max_delta'];
    while ($delta <= $max_delta) {
      // prepopulate whatever you like with help of #default_value'
      $form['--machine-name-of-field--'][LANGUAGE_NONE][$delta]['#default_value'] = 'Value to show';

      $delta++;
    }
  }
}

So thats it basically. Hope that this solution will help some people to save time...

3

I've found a way to do this that is entity-agnostic (works on more than just node forms), and doesn't require any form rebuilding:

function mymodule_form_alter(&$form, &$form_state, $form_id) {
  if($form_id == 'my_form_name') {
    $items_count = 6;
    $field_name = 'field_field_name';
    $form_theme = $form[$field_name][LANGUAGE_NONE]['#theme'];
    // Remove delete and add more buttons
    $form[$field_name][LANGUAGE_NONE][0]['remove_button']['#access'] = FALSE;
    $form[$field_name][LANGUAGE_NONE]['add_more']['#access'] = FALSE;
    $items = &$form_state['field'][$field_name][LANGUAGE_NONE];
    // Generate required number of fields collection
    if ($items_count > 1 and $items['items_count'] != $items_count) {
      $items['items_count'] = $items_count;
      $items['field']['cardinality'] = $items_count;
      $form[$field_name][LANGUAGE_NONE] = field_multiple_value_form($items['field'], $items['instance'], LANGUAGE_NONE, array(), $form, $form_state);
        // Reset theme function, as field_multiple_value_form hijacks it
        $form[$field_name][LANGUAGE_NONE]['#theme'] = $form_theme;
    }
    // Fill generated fields with data
    for ($delta = 0; $delta < $items_count; $delta++) {
      $form['field_course_preferences'][LANGUAGE_NONE][$delta]['field_user_qualified_adjusted'][LANGUAGE_NONE][0]['value']['#default_value'] = 'new value';
      }
  }
}

The trick is in the field_multiple_value_form, which attaches the extra widgets properly so that the form doesn't fail validation, but lets you do a straight copy from the first form item. (Note, some of this stolen from others in the thread. :)

3
  • disabling of remove_button not working when this is implemented via ajax select element reference. only first time remove button gets disbled. any clue?
    – kiranking
    Commented Mar 25, 2015 at 19:55
  • Thanks @Renee! I'd love to get this working with $items['field']['cardinality'] = -1. Because the item count is hard-coded, adding or removing field collections doesn't work. Did you have a solution for this? Commented Feb 1, 2017 at 12:09
  • @Renee, I too have same question, I need Add and remove button. I commented line $items['field']['cardinality'] = $items_count; to preserve cardinality as -1, but add more button gives error : EntityMalformedException: Missing bundle property on entity of type field_collection_item. in entity_extract_ids()
    – neha
    Commented Feb 3, 2017 at 11:26
0

So the form attribute I think you're looking for is the #default_value. Populating this attribute for a given form element, will set it's default value, e.g. what appears in the field when the form is rendered.

You'll probably run a loop to generate the form for page two, as the number of files in the source zip will vary. Within this loop you will generate at least one, perhaps multiple form fields for each file. The field you want prepopulated should look something like this:

$form['file_meta_form'][$file_count]['prepopulated_textfield'] = array(

  '#title' => t('File info'),

  '#type' => 'textfield',

  '#default_value' => $file_meta_info, 

  '#description' => t("Helpful instructions here"),

  '#size' => 50,

  '#required' => TRUE,

);

The form API page looks a little daunting at first, but if you are searching for specifics it is a valuable resource. Also check out the examples module as that's where I learned everything I know about forms. You can also control the all the varying details of the above form field by changing the attributes as desired. Again, the form API will be a good reference.

Update: Oh, and about saving to the db; I don't think you will need to do anything special as the default form submission and data storage should do everything you need. More directly, yes all the prepopulated fields will be saved to the db as with any other field. (if I understand what your after)

I'm new to answering, so just comment if you need clarification or...

Not the answer you're looking for? Browse other questions tagged or ask your own question.