I have a form with a field collection "field_definition" which has 2 fields in it with unlimited cardinality on the field collection.

I am creating a multistep node form. The first step is to upload the a spreadsheet. Based the information in the spreadsheet, I want to pre-populate the field collection with values.

For example: (each pair is the field collection definition, which has unlimited cardinality in the form)

Field Name: One
File Type: Integer

Field Name: Two
Field Type: String

Field Name: Three
Field Type: Interger
..
..

I tried this in form_alter

$field_definition_template = $form['field_definition']['und'][0];

for($k=0;$k<count($column_names);$k++)
{
    $form['field_definition']['und'][$k] = $field_definition_template;
    $form['field_definition']['und'][$k]['#delta'] = $k;
}

There are two problems with this....form_alter gets called when clicking "Add another item" and there are a lot of attributes I would have to change in order to get this to actually work correctly. Is there an API or another way to do what I want to accomplish?

Comments

grasmash’s picture

I'd love to know this as well. I'm attempting the same thing.

loze’s picture

Did you ever figure this out?

Trying to prepopulate a multi value field_collection also.

lukus’s picture

I'm trying to work out the same thing - I'll post back here if I find out.

EDIT:

<?php

// $node contains the node array.

// create an array for field collection definiton

$values = array(
   'field_name' => 'fieldcollection_name',
      
   // the field defined in fieldcollection
   'field_myfield' => array(                                           
      LANGUAGE_NONE => array(array('value' => 'Hello')),
   ),
);
  
// create the field_collection item
$field_collection_item = entity_create('field_collection_item', $values);

// associate it with the node array
$field_collection_item->setHostEntity('node', $node);

// save the field collection
$field_collection_item->save();

?>
yogeshchaugule8’s picture

Did anyone found solution for this, I've same problem and want to pre-populate the field collection field.

SaV’s picture

I am also very interested. Having the same problem and no solution yet...

SaV’s picture

After hours of debugging I found a good working solution!

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...

robcarr’s picture

To target the actual field within the Field Collection, the final function needs to be altered:

<?php
// 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]['--machine-name-of-field-within-field-collection--'][LANGUAGE_NONE][0]['value']['#default_value'] = 'Value to show';
      $delta++;
    }
  }
}
?>

Thanks for posting your solution SaV - real lifesaver

** EDIT

One problem I had found was an error if Field Collection Table module is used. See #1815496: Using "Hide blank items" leads to misformed tables for more info. Simplest option (for me) was to uncheck 'Hide blank items' box on Content Type field settings.

jufran20’s picture

This code is great, but I have a error when I rebuild node edit form. The fields generate OK but the "Add another item" button fails.
My code is:

function my_module_custom_node_edit($node) {
module_load_include('inc', 'node', 'node.pages');
  $form_id = 'mycontent_node_form';
  $form_state = array();
  $form_state['build_info']['args'][] = $node;
  $form_state['build_info']['files'][0] = 'modules/node/node.pages.inc';
  $form_state['build_info']['files']['menu'] = 'sites/all/modules/ctools/page_manager/plugins/tasks/node_edit.inc';
  $form_state['want form'] = TRUE; 
  $form = drupal_build_form($form_id, $form_state);
  $form_state['cache'] = FALSE;
  // how many items do we want?
   $num_items = 5;
    $form_state['my_module_prepopulate'] = TRUE;
  // this is exactly what the ajax request for the add more button does
  $form_state['field']['my_field_multiple'][LANGUAGE_NONE]['items_count'] = $num_items;
  drupal_static_reset('drupal_html_id');
  return drupal_rebuild_form($form_state['build_info']['form_id'], $form_state, $form);
}

And the warrings are:

Warning: array_keys() [function.array-keys]: The first argument should be an array en EntityReference_SelectionHandler_Views->validateReferencableEntities() (línea 141 de /home/mydir/public_html/system/sites/all/ modules/entityreference/plugins/selection/EntityReference_SelectionHandler_Views.class.php).
Warning: array_flip() [function.array-flip]: The argument should be an array en entityreference_field_validate() (línea 223 de /home/mydir/public_html/system/sites/all/modules/entityreference/entityreference.module).
Warning: array_diff_key() [function.array-diff-key]: Argument #2 is not an array en entityreference_field_validate() (línea 223 de /home/mydir/public_html/system/sites/all/modules/entityreference/entityreference.module).

I hope to help me.

Renee S’s picture

I've found a different way to do this that is a bit more form-agnostic (not limited to node forms, no 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'] = '4';
	        }
    }
}

>

El Bandito’s picture

So far this works for me although the last assignment statement needs to be changed thus :

$form[$field_name][LANGUAGE_NONE][$delta]['field_user_qualified_adjusted'][LANGUAGE_NONE][0]['value']['#default_value'] = '4';

I also found that to set the default value I had to target a different position in the array ( not below ['value'] ) thus :

$form[$field_name][LANGUAGE_NONE][$delta]['field_user_qualified_adjusted'][LANGUAGE_NONE][0]['#default_value'] = '4';

Renee S’s picture

This will depend on the field type -- some of them prefer ['number'] instead of ['value'] and others can be set directly.

nithinkolekar’s picture

$form['field_course_preferences'][LANGUAGE_NONE][$delta]['field_user_qualified_adjusted'][LANGUAGE_NONE][0]['value']['#default_value'] = '4';

where field_course_preferences is multivalue field collection and field_user_qualified_adjusted is subfield of field collection right? then field_field_name should be field_course_preferences.

ndrosev’s picture

Thanks that works for me

ropic’s picture

After spending hours your solution is the best

AaronELBorg’s picture

What's the trick to keep the 'remove' and 'add more' buttons after setting the desired amount of field collection slots?

  $form[$field_name]['und'][0]['remove_button']['#access'] = TRUE;
  $form[$field_name]['und']['add_more']['#access'] = TRUE;

...is not working. Thanks!

nehajyoti’s picture

This solution worked, but i don't need to remove "REMOVE" and "ADD ANOTHER ITEM" button . That's why i have not included following lines in my code.

// 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;

The issue for me is that neither of these remove or Add item button is working.

sethu_sk’s picture

Hi,

Specified item count is generated but 'add another item' button is not working. I try to fix and unable to add an additional item.

Kindly do the needful.

nehajyoti’s picture

Solution posted by SaV : This solution works perfect for me BUT i don't want to use drupal_build_form as used above because using drupal_build_form will rebuild the form, hence page load time will be increased and performance of will decrease. Does any body have any other solution ??

wernerglinka’s picture

I tried Renee's solution and it seemed to work fine but when I use search and the search term is associated with a node that uses these multiple values, the sites crashes. Is anybody seeing this as well?

Renee S’s picture

Are you targetting the form correctly? This shouldn't interfere at all with any other functionality. Either that or see above re: how to set the value; it might require using a slightly different form element in the render array to set #default-value.

eta: off the top of my head, I believe you need to use ['tid'] to set taxonomy terms? I think. Anyway. That :)

shree.yesare’s picture


function yourmodulename_form_alter(&$form, &$form_state, $form_id) {
  
  switch($form_id) {
    case 'form_id':        
     
      $items_count = 6;
      $field_name = 'field_field_name';
      
      $form_theme = $form[$field_name][LANGUAGE_NONE]['#theme'];
      $items['items_count'] = $items_count;
      $items['field']['cardinality'] = $items_count;
      $items['field']['field_name'] = $field_name;
      $items['field']['columns'] = Array(
        'value' => Array(
            'type' => 'int',
            'not null' => '',
            'description' => 'The field collection item id.',
        ),
        'revision_id' => Array(
            'type' => 'int',
            'not null' => '',
            'description' => 'The field collection item revision id.',
        ),
      );
      
      $entity_type = 'node';      
      $bundle_name = 'bundle_name'; // Replace this with your field bundle name
      
      // Field Instance
      $items['instance'] = field_info_instance($entity_type, $field_name, $bundle_name);
      
      $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;      
    
      $form[$field_name][LANGUAGE_NONE][0]['remove_button']['#access'] = FALSE;
      $form[$field_name][LANGUAGE_NONE]['add_more']['#access'] = FALSE;
 
    break;
  }
}

nithinkolekar’s picture

Instead of loading multifield default values again on node/edit form how to disable remove_button?

if (!isset($form['#node']->nid)) { 
// code to create multifield value and load default 
// work great
}
else {
// node edit mode
//how to disable remove_button here?
}
Jaypan’s picture

That's a different question to the original topic, and therefore you should start a new topic on the matter.

maverick619’s picture

The suggestions for this problem work great. My only issue is that I want to keep this field as unlimited cardinality with a number of "suggestion" fields already displayed to the user when they create a new node. This means I still require the "add another item" and "remove" buttons. The problem now is that these buttons are all broken. Is there any way to pre-set the number of values I have initially and give them data while still keeping the original functionality of an unlimited field?

** EDIT

I must also say that I'm programmatically loading the form into a custom page as I need the same url for the node create and edit page. If going straight through the node create page it works fine. When in the custom page it loses access to node.pages.inc when running drupal_retrieve_form() and a mass of php errors come originally from node_form not being a valid callback. Any ideas as to why its no longer using the node functions and how I can force it to use them?

** EDIT

For anyone interested I fixed it by including node.pages.inc when field_collection/ajax is called from my hook_init().

<?php
if ($_GET['q'] == 'field_collection/ajax' && preg_match('/^[a-z_]+_node_form/', $_POST['form_id'])) {
  module_load_include('inc', 'node', 'node.pages');
}
?>
kavithasethu’s picture

I also followed Renee S post and it is populating everything as needed. But am missing "Add another item" link . I commented out

$form[$field_name][LANGUAGE_NONE][0]['remove_button']['#access'] = FALSE ;
$form[$field_name][LANGUAGE_NONE]['add_more']['#access'] = FALSE;

these lines ..But still not working... Any suggestions??

Thanks,
Kavitha

maks9889’s picture

Function "field_multiple_value_form()" deletes "#field_parents" and "#language" from field and it breaks first usage of the "Remove item" button. This is working code from my project:

$field_name = 'field_gather_evid';
      $form_theme = $form[$field_name][$language]['#theme'];
      $field_parents = $form[$field_name][$language]['#field_parents'];//important!!
      $field_language = $form[$field_name][$language]['#language'];//important!!
      $items = &$form_state['field'][$field_name][$language];
      $checked_boxes = $node_wrapper->field_evaluation_methods_xx->field_evaluation_methods_yy->value();
      // dpm($checked_boxes);
      $new_method_names = array();
      // look for new checked methods in "field_evaluation_methods_xx"
      foreach ($checked_boxes as $method_name) {
        $elt_exist = null;
        foreach ($form[$field_name][$language] as $key => &$elt) {
          if (is_int($key)) {
            // dpm($elt['field_method'][$language]['value']);
            if (isset($elt['field_method']) && trim($elt['field_method'][$language][0]['value']['#default_value']) == trim($method_name->name)) {
              $elt_exist = $elt['field_method'];
            }
          }
        }
        if ($elt_exist == null) {
          $new_method_names[] = trim($method_name->name);
        }
      }
     
      //add new items to the "$field_name"
      $count_new_method_names = count($new_method_names);
      // ignore ajax request
      if ($count_new_method_names > 0 && !isset($form_state['values'])) {
        $items_count_old = $items['items_count'];
        $items['items_count'] += $count_new_method_names;
        $form[$field_name][$language] = field_multiple_value_form($items['field'], $items['instance'], LANGUAGE_NONE, array(), $form, $form_state);
        $form[$field_name][$language]['#theme'] = $form_theme; // not important
        $form[$field_name][$language]['#field_parents'] = $field_parents; //important!!
        $form[$field_name][$language]['#language'] = $field_language; //important!!
        // Fill generated fields with data
        for ($delta = 0; $delta < $count_new_method_names; $delta++) {
          $form[$field_name][$language][$items_count_old + $delta]['field_method'][$language][0]['value']['#default_value'] = $new_method_names[$delta];
        }
      }
vishal.sirsodiya’s picture

Hi maks9889,
How to Get $node_wrapper in form alter.

nithinkolekar’s picture

this code is incomplete because variables like $language and $node_wrapper are not defined and showing notice like Notice: Undefined variable: language.

nithinkolekar’s picture

when both source and destination field collection is of same instance and machine name, should we have to loop through multivalue field collection when we have to load field collection value from already submitted node.

ex:

field_fc_test (on node preexisting data)
[0]
--fc_text1 : "some text"
--fc_text2 : "another text"
[1]
--fc_text1 : "second text"
--fc_text2 : "second another text"

If same field_fc_test is added to another bundle with same cardinality and if we need to populate on _form_alter shall we have to loop through node's field_fc_test field?

i.bajrai’s picture

Here's what I did to populate a field_collection form reading the values off a product and applying it to a line item.


  if (strpos($form_id, 'commerce_cart_add_to_cart_form') !== FALSE) {
    //Configure the dimensions of the line item
    $product = entity_metadata_wrapper('commerce_product', $form['product_id']['#value']);
    $dimensions = &$form['line_item_fields']['field_dimensions']['und'];

    $field_name = $dimensions['#field_name'];
    $langcode = $dimensions['#language'];
    $parents = $dimensions['#field_parents'];
  
     //A lot of the hard work can be gotten from this function.
    $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);
    $field_state['items_count'] = count($product->field_dimensions); //This is extremely important to set.
    $field_state['field']['cardinality'] = count($product->field_dimensions); //This is extremely important to set.

    //I feel   field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state); could
    //work here too...
    $dimensions = field_multiple_value_form($field_state['field'], $field_state['instance'], LANGUAGE_NONE, array(), $line_item_fields, $form_state);
 
     //Setting up the default values.
    foreach ($product->field_dimensions->getIterator() as $delta => $dimension){
      if(!isset($dimensions[$delta])){
        continue;
      }
      $dimensions[$delta]['field_dimension']['und']['#default_value'] = $dimension->field_dimension->getIdentifier();
      $dimensions[$delta]['field_value']['und'][0]['value']['#title'] = $dimension->field_dimension->label();
      $dimensions[$delta]['field_dimension']['#access'] = FALSE;
      $dimensions[$delta]['remove_button']['#access'] = FALSE;
    }
  }


BDuell’s picture

I attacked this issue this way...

// In your hook_form_FORM_ID_alter() function...
	
  // Our field and the number of values that we need to have in it...
  $field_name = 'field_machine_name';
  $items_count = count($array_of_values);
	
  // If we have an array of values that need to be prepopulated...
  if (!empty($array_of_values) && $form_state['field'][$field_name][LANGUAGE_NONE]['items_count']!=$items_count) {
    $form_state['field'][$field_name][LANGUAGE_NONE]['items_count'] = $items_count;
    $form_theme = $form[$field_name][LANGUAGE_NONE]['#theme'];
    $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;
    }
      
    // Tell our form that we need to rebuild once we are done...
    $rebuild_p = TRUE;
  }
	
  // ANY OTHER FORM ALTER STUFF
	
  // If we need to rebuild, notify the form...
  if ($rebuild_p) $form['#after_build'][] = 'MYMODULE_after_build';

... and then my custom MYMODULE_after_build function...

/**
 * Rebuild the form if necessary...
 */
function MYMODULE_after_build($form, &$form_state) {
  return drupal_rebuild_form($form_state['build_info']['form_id'], $form_state, $form);
}
fakir22’s picture

Thanks to Renee and BDuel, here is a working code to pre-populate the fields AND keep the Add/Remove item functionality. Basically, you have to remove those lines from Renee's example, and let the cardinality to -1 for unlimited items

// Remove delete and add more buttons
$form[$field_name][LANGUAGE_NONE][0]['remove_button']['#access'] = FALSE;   // DON'T NEED THAT
$form[$field_name][LANGUAGE_NONE]['add_more']['#access'] = FALSE;	          // DON'T NEED THAT EITHER
$items['field']['cardinality'] = $items_count; // DON'T NEED THAT : if you set a number here, the ADD MORE and REMOVE buttons will not show up

AND

rebuild the form following BDuel's example ;)

Here is a complete example :

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
		// DON'T NEED THAT
	//	$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;
		  // DON'T NEED THAT : if you set a number here
		  // the ADD MORE and REMOVE buttons will not show up
	//	  $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;
			// Tell our form that we need to rebuild once we are done...
	  		$form['#after_build'][] = 'MYMODULE_after_build';  // WE NEED THAT : to tell the form he need to rebuilt
		}

		// 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'] = '4';
	    }
	}
}


/**
 * Rebuild the form when we're done
 */
function MYMODULE_after_build($form, &$form_state) {
  return drupal_rebuild_form($form_state['build_info']['form_id'], $form_state, $form);
}

Hope this helps ;)

lazzyvn’s picture

thanks fakir22 alots you save me a lot of time

$form[$field_name][LANGUAGE_NONE] = field_multiple_value_form($items['field'],$items['instance'],LANGUAGE_NONE, array(),$form,$form_state );
$form['#after_build'][] = 'mymodule_after_build';

this is a key why i did see anything in my form