I am submitting a form via AJAX to a custom menu hook for processing. With every other node element I can simply set the value of the field based on the REQUEST values sent..
Eg:

<?php
$node
->field_hello[LANGUAGE_NONE][0] = 'world';
?>

and then call node_save($node). I thinking your module relies on using the _validate and _submit hooks to work properly? I've tried the following with no luck:

<?php
    $form_state
= array();
   
$form_state['values'] = $values; //values from request object
   
$form_state['build_info']['args'] = array($node);
    try {
     
drupal_form_submit('form_field_profile_exp_type', $form_state);
    } catch (
Exception $e) {
     
dpm(array('error:'=>'Failed to save new data. Check the log for details.', 'Exception'=>$e));
     
$return = _fp_ipe_error();
    }
   
dpm(form_get_errors(), 'form errors ?');
?>

Any ideas?

Comments

Priority:Major» Normal

Changing the priority as support requests are never "major", see Priority levels of issues for details.

Title:How does one programatically save field collection field data?How does one programatically submit new field collection field data (CRUD)?

Not really..

There is a lot of support on "adding field collections to nodes/entities" and "saving/deleting field collection field data in code" but they are not really what I need.

My scenario:
I have a custom form making use of the node edit form and extracting a subsection of fields. In this case just the field collection fields...

<?php
// store the complete form in a dummy array and extract what we need below
 
$complete_form = array();
 
field_attach_form('node', $node, $complete_form, $form_state);
 
$form['field_my_collection'] = $complete_form['field_my_collection'];
  return
$form;
?>

At the moment, I'm having to re-implement client side logic for adding a new entry or removing an entry. That logic is partially working.

This works for updating existing values:

<?php
    $form_state
= array();
   
$form_state['values'] = $values;
   
// pass in the node for our form to use
   
$form_state['node'] = &$node;
   
$form_state['build_info']['args'] = array(&$node);
   
// this is currently generating a watchdog notice but doesn't stop us working
    // .."Notice: Undefined offset: 0 in file_field_widget_form() (line 513 of /modules/file/file.field.inc)."
   
drupal_form_submit('form_my_custom_form', $form_state);
   
node_save($node);
?>

I'm assuming that if I send the correctly formatted $_REQUEST/POST values then all entries should be updated/deleted/added (CRUD). Only RU is working, CD not yet. I'm not sure how to get it to recognise new entries or delete old ones without emulating the built in field collection module ajax callbacks. I can't really use those ajax callbacks from the module in my custom AJAXified setup.

I'm running into a similar problem and dynamicdan I'm curious if you found a solution?

I have a custom / dynamically generated list field that works perfectly well when it is stand alone. As soon as I dump it inside a field collection, I am not able to actually save any of the selections that the user makes. I still see my custom list item, but even in the pre-save hook, there is nothing captured when the user hits the submit button.

I still don't have a solid solution.

I have trouble with adding and deleting entries. I basically need a pure AJAX solution for my system.

I've added a fairly detailed writeup over on the normal forums about what I'm trying to do. https://drupal.org/node/1854524

I hava a solution..

On the client side I do some fancy custom jquery clone stuff to get a new entry for the user. The new entries have no _weight property (weights are not needed for me). By default, the first empty row saves fine on it's own but the new ones need some help...

<?php
if(!isset($f['_weight'])) {
 
$field_collection_item = entity_create('field_collection_item', array('field_name' => $fc_field_name));
 
$field_collection_item->setHostEntity('node', $node);
  foreach(
$f as $f_name => $val) {
   
$field_collection_item->{$f_name} = $val;
  }
 
$field_collection_item->save();
}
?>

I think this has something to do with some default FC entity preparing it does in my standard 'field_attach_form()' node form.

Then I do my normal save stuff as previously mentioned..

<?php
    $form_state
= array();
   
$form_state['values'] = $_REQUEST;;
   
$form_state['node'] = &$node;
   
$form_state['build_info']['args'] = array(&$node);
   
drupal_form_submit('form_field_my_custom_form', $form_state);
    try {
     
node_save($node);
    } catch (
Exception $e) {
     
watchdog('fp_ipe', 'Failed to save new data.', array(), WATCHDOG_ERROR);
    }
?>

Deleting is handled client side by simply clearing the input values for the fields and then hiding the rows (so that they can still be submitted).

So I have C, U, D. I guess R is working in the CRUD.

Would have been nice is there were some standard functions/hooks provided like hook_field_collection_save_form() and standard client side JS to insert a new entry.

FYI, here's my client side JS for cloning a row:

    $rendered.find('.field-add-more-submit.form-submit').click(function(ev) {
      var $table_body = jQuery(ev.currentTarget).closest('.form-item').find('table:first tbody');
      var $clone = $table_body.find('tr:last');
      var clone_code = "<tr>" + $clone.html() + "</tr>";
      // the clone should be a delta++ Delta indexes need to make sense otherwise
      // labels will not work correctly when clicked and inputs will not save correctly
      // und-2-field
      clone_code = clone_code.replace(/und-([\d*])-field/g, function(match, contents, offset, string) {
        return 'und-' + (++contents) + '-field';
      });
      // und-2-remove
      clone_code = clone_code.replace(/und-([\d*])-remove/g, function(match, contents, offset, string) {
        return 'und-' + (++contents) + '-remove';
      });
      // [und][2][field
      clone_code = clone_code.replace(/\[und\]\[([\d*])\]\[field/g, function(match, contents, offset, string) {
        return '[und][' + (++contents) + '][field';
      });
      $clone = jQuery(clone_code);
      // possibly still need to reset input/textarea/select etc. values.
      $table_body.append($clone);
      /* prevent default action and bubbling */
      ev.preventDefault();
      ev.stopPropagation();
      return false;
    });

I've done a bit of experimenting here, and I did have some progress submitting against a field collection with a custom field. In my case, I was able to establish a list of allowed values ahead of time (something like 40-50 different values) and depending on the user, I would show that user a subset of values. So my steps are something like...

1. Create Content Type
2. Add Field Collection Field
3. Add List (Text) Field to Field Collection
4. Add Allowed Values List to Field Collection Field
5. Use code below to modify the Field Collection Field to only show certain values to the user when they pull up the form.

In doing it this way, all of the core field code still kicks in and works as intended, I'm just altering the display of what the user is seeing when they are interacting with the form. Beyond that, the field is still a legit field and works the way Drupal intended. Works in this case, may not work in all if you aren't using a list field.

<?php
function hook_form_alter(&$form, &$form_state, $form_id){
$i=0;
   
//Foreach field collection that we have...
    
foreach ($form['field_goal']['und'] as $key=>$value) {
      
//if it's one of the actual collections and not another part of the array
      
if (is_numeric($key)) {
        
//add a custom field into the form based on the key
        
$form['field_goal']['und'][$key]['field_goal_category']['und']['#options'] = $values;
       }
      
$i++;
     }
return
$form;
?>

In the example above, the "field_goal" is my field collection and the "field_goal_category" is my custom list field. I am essentially replacing ONLY the values in the field with my custom $values array (defined outside of this code) and then returning the form without monkeying around with anything in the database.

The user makes a selection, which is valid, and we move on with out lives.