4

This is an extension of this question - "How to pre-populate multiple "field collection" fields in node form"

I'm trying to do the same thing, but I'm trying to get it to work on an ajax call rather than on the form load.

The form loads, the user selects an option on a dropdown and then the form automatically populates several field collections with ajax.

The Problem

The existing solution on the question above involves altering the form when it's first built, by changing:

 $form_state['field']['--machine-name-of-field--'][LANGUAGE_NONE]['items_count']

And then rebuilding the form using the new form state values.

The problem with an ajax call is that, it seems to ignore any changes made to $form_state in the callback. i.e.

function ajax_get_parent_field_collection ($form, &$form_state) {
  $form_state['field']['field_collection_entity']['und']['items_count'] = '3';
  return $form['field_collection_entity'];
}

The changes made in $form_state above are ignored, so when the form is rebuilt nothing happens and if you then dump $form_state none of the changes made are saved.

I could manually create the field collection entities and insert them, but I was hoping there's a cleverer answer. Has anyone tackled this before?

EDIT:

The ajax is currently called on a select field, so the ajax triggers when the select dropdown is used.

$form['field_company_for_center']['und']['#ajax'] = array (
  'callback' => 'ajax_get_parent_field_collection',
  'wrapper' => 'ajax-wrapper',
  'effect' => 'fade',
);
3
  • How do you trigger ajax? Do you have an #ajax attribute on some element? Is it a submit button?
    – milkovsky
    Commented Jan 5, 2015 at 21:56
  • @milkovsky It's an ajax attribute on a select element. The end idea is that they select a node reference value. The ajax loads the node that was referenced, creates a bunch of empty field collections and populates them with the values from the node. Commented Jan 5, 2015 at 21:58
  • You can enter a chat here, to discuss chat.stackexchange.com/rooms/20015/…
    – milkovsky
    Commented Jan 5, 2015 at 22:15

2 Answers 2

2
+100

The form needs to be build again after items_count is changed.

function MYMODULE_form_alter (&$form, &$form_state, $form_id) {
  if($form_id == 'MYFORM'){
    $form['#attributes']['id'] = 'MYMODULE-form-ajax-wrapper';

    // Handle triggered ajax element.
    if(!empty($form_state['triggering_element']['#array_parents'][0])) {
      if ($form_state['triggering_element']['#array_parents'][0] == 'field_node_reference') {
        $form_state['field']['field_collection_entity'][LANGUAGE_NONE]['items_count'] = '3';
      }
    }

    // Add ajax callback.
    $form['field_node_reference'][LANGUAGE_NONE]['#ajax'] = array (
      'callback' => 'MYMODULE_ajax_rebuild_form',
      'wrapper' => 'MYMODULE-form-ajax-wrapper',
    );
  }
}

/**
 * Ajax callback, rebuilds and returns the entire form.
 */
function MYMODULE_ajax_rebuild_form ($form, &$form_state) {
  return drupal_rebuild_form($form_state['build_info']['form_id'], $form_state, $form);
}
2

Some surrounding thoughts and things that came up when discussing this with milkovsky. The problem looks like it came from the form not being re-built correctly.

As mentioned above any changes made to $form_state in the ajax callback seem to get erased when everything is returned.

That's the reason for the conditional. Because you can't set values in $form_state in the ajax call instead you put a conditional in hook_form_alter which will only run after the ajax call has been re-made.

But by explicitly returning drupal_rebuild_form, it appears that changes made to $form_state in the ajax callback are saved.

So conditional can be removed and callback changed to:

function MYMODULE_ajax_rebuild_form ($form, &$form_state) {
  $form_state['field']['field_collection_entity'][LANGUAGE_NONE]['items_count'] = '3';
  return drupal_rebuild_form($form_state['build_info']['form_id'], $form_state, $form);
}

You can't set $form_state['rebuild'] = TRUE in the ajax callback because it's not saved (because the form isn't rebuilt properly, which ironically is what we're trying to accomplish).

I tried putting the $form_state['rebuild'] = TRUE into a submit handler and forcing the select field to call the submit as shown here, but that also didn't work.

If you're also trying to pre-populate the form

By default ajax reloads a cached version of the form, so changes made to default values in hook_form_alter won't get applied when the form is rebuilt by ajax even though they'll appear in the $form if you print it out.

In order force a non-cached form you can wipe $form_state['input'] in the ajax callback i.e.

$form_state['input'] = array();

This solution came from Reset node form after AJAX submit and you can find more detail on that question.

Hopefully the extra explanation is useful and if you can add any more please do!

A second method for loading and pre-populating field collections

I've come across a second method to do this which (at least in my situation) is more useful than the first. I'm trying to pre-populate field collections from another node and another way to do this, is to create a fake form instance and then plug it into the original form.

The method came from the final part of this blog post: http://drupal.cocomore.com/blog/field-collections-exposed

The only real differences between this example and the blog one is that this one builds the fake form in an ajax callback and rather than looping through all the fields it only changes a single one.

function ajax_get_parent_field_collection ($form, &$form_state) {
  //1. load parent node
  $parent_node = node_load($form_state['values']['field_node_reference'][LANGUAGE_NONE][0]['nid']);

  //prebuild fields
  $node_fields =  field_info_instances('node', 'field_collection_entity');

  //Re-create a fake form with all the trimmings which we can then attach
  //as a replacement.
  $tmpform = array();
  $tmpform['#node'] = $parent_node;
  $tmpform['type']['#value'] = 'node_type';
  $tmpform_state = array('build_info' => array('form_id' => $form['#form_id']) );
  field_attach_form('node', $parent_node, $tmpform, $tmpform_state, entity_language('node', $parent_node));

  //replace current form with our custom version
  $form['field_collection_entity'] = $tmpform['field_collection_entity'];
  $form_state['field']['field_collection_entity'] = $tmpform_state['field']['field_collection_entity'];
  $langcode = field_language('node', $parent_company, 'field_collection_entity');

  //iterate over the fields resetting the item and revision id so new
  //field collections are created rather than linking existing fc's
  $field_childs = &$form_state['field']['field_collection_entity'][$langcode]['entity'];
  foreach(element_children($field_childs) as $idx => $fc_entity) {
    $field_childs[$idx]->item_id = NULL;
    $field_childs[$idx]->revision_id = NULL;
  }

  //reset input to make sure cached version of the form isn't used
  $form_state['input'] = array();
  return drupal_rebuild_form($form_state['build_info']['form_id'], $form_state, $form);
}
1
  • found very useful for loading multivalue field collection but couldn't reset the FC field to its default state(empty subfields) when triggering_element's(field_node_reference) value doesn't return any records using entityfieldquery. How to reset FC field to initial form state(prior ajax call)
    – kiranking
    Commented Mar 24, 2015 at 19:28

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