The only way I have found to do custom validation on field collections is by prepending a validation hook to the element_validate property of the widget. Below is an example on how I did this.

/*
 * Adds a validation hook to the beginning of the #element_validate
 * to provide custom validation on the required fields within the
 * widget.
 */
function trip_planner_field_widget_form_alter(&$element, &$form_state, $context) {
  switch ($context['instance']['widget']['type']) {
    case 'field_collection_embed':
      // @todo check for field name
      array_unshift($element['#element_validate'], 'trip_planner_validate_line_item');
      break;
  }
}

/*
 * Validate our from and to destinations from our field collection
 * widget.  We need to use form_error on each field to fire off the
 * necessary errors.
 */
function trip_planner_validate_line_item($element, &$form_state, $form) {
  form_error($form['field_from_to'][LANGUAGE_NONE][0]['field_tee_time'], 'test');
}

If you have unlimited values, you will need to iterate over your container (which I will be doing next if anyone is interested). The reason why it needs to be done this way is because the function field_collection_field_widget_embed_validate() validates and submits all in once, so if you add a validation hook anywhere else, except before this function, it just gets ignored.

Comments

charlie-s’s picture

Does the above code add the custom validation handler to every single embedded field collection on that site? I ask because I'm trying to properly add custom validation to field collections and am having a hard time, but the case 'field_collection_embed': appears to me that it would throw this on every embedded field collection widget.

iLLin’s picture

Yea it should, you will just need to filter it inside the validate hook for your field collection.

erinclerico’s picture

I would like to see your example of unlimited values - thank you very much.

I'm having some trouble getting at the correct approach with my implementation of hook_form_alter for the inline collection fields using dsm() -

function schoolmeal_form_alter(&$form, $form_state, $form_id) {
  dsm ($form_id);
}

This returns nothing to the screen (except for the base node form id) so I'm a little in the dark in terms of exactly how to manipulate my $form (for the inline collections) for the validation I need to add.

Thank you in advance.

-Erin

darvanen’s picture

I managed to iterate through and even target specific items in the field collection in Drupal 8 like this:

function MODULENAME_FORMID_form_validate(array &$form, \Drupal\Core\Form\FormStateInterface $form_state) {
  $items = $form_state->getValue('field_collection');
  foreach($item as $key => $item){
    if (is_int($key)){
      // Is there a file uploaded?
      $file = count($resource['field_document'][0]['fids']);
      // Is there a link?
      $link = (strlen($resource['field_link'][0]['uri']) ? 1 : 0);

      // Make sure there is one and only one value
      if ($file + $link != 1){
        $form_state->setError($form['field_collection']['widget'][$key], t('Error message'));
      }
    }
  }
}