One of the most useful features of field collection is the ability nest collections within each other.
However, theming these nested collections can be difficult, especially as there doesn't seem to be much documentation about it.

I hired the excellent Rob W to produce a solution. I have successfully used his solution on two projects (Elite Energy and Hirsch and Co Accountants).

I have posted it below, as I am sure it will help many people.

Put this code in your template.php file (remember to go to admin/config/development/performance and flush the caches after making any changes to template files):

<?php

/** 
* This function takes a field collection field and returns an object array that contains all of the field 
* collection entity's fields. You can then use it the same way you would a node object. For example:
* 
* $loaded_field_collection =  load_collection($node->your_field_collection);
* dsm(loaded_field_collection); //check it out, there's all your fields.
* print 'check this ' . $loaded_field_collection->your_collected_field['und'][0]['value'] . ' value, sucka!';
* 
*/
function load_collection($entity_uri) {
  $loaded_field_collection = entity_load('field_collection_item', array(0 => $entity_uri));
  foreach ($loaded_field_collection as $loaded_field_collection_object ) {
    return $loaded_field_collection_object;
  }
}

You can then use the following code in your node.tpl.php file:

<?php
/**
*
* For this example my content type was configured like this:
*
*  node
*   - top_level_collection
*     - second_level_collection
*	    - text_1
*	    - image_1
*	    - third_level_collection
*         - text_2
*         - image_2
*     - second_level_collection_2
*	    - text_3
*	    - list_1
*	    - image_3
*/

// check if field collection is not empty. If content exists, do some stuff.
if (!empty($node->top_level_collection['und'])) {

  //Your custom wrapper goes here
  print '<div class="collectin-it">';
  
  foreach ($node->top_level_collection['und'] as $a_top_level_collection) {
    //Use the load_collection() function to load your top level field collection's entity into an object.
    $top_level = load_collection($a_top_level_collection['value']);
  
    //You can use Devel's dsm() to view this object in the Devel tab of your node. Comment out or delete when everything's working well.
    dsm($top_level);
  
    //if your top_level_collection contains any fields, you can print them here using the $top_level object. Example: print $top_level->your_field['und'][0]['value'];
  
    //Use the load_collection() function to load your second level field collection entity object from the field in the top level field collection's entity object. All notes from the preceeding level apply.
    foreach ($top_level->second_level_collection['und'] as $a_second_level_collection) {
      $second_level = load_collection($a_second_level_collection['value']);

      dsm($second_level);
	  
      foreach ($second_level->text_1['und'] as $a_text_1) {
        print '<p>' . $a_text_1['safe_value'] . '</p>';
	  }
	  foreach ($second_level->image_1['und'] as $a_image_1) {
        $var = array(
          'item' => $a_image_1,
          'image_style' => 'large',
          'path' => ''
        );
        print theme_image_formatter($var);
	  }

      //Just like above, you can load the next level from the field in this level's field collection entity object.
      foreach ($second_level->third_level_collection['und'] as $a_third_level_collection) {
        $third_level = load_collection($a_third_level_collection['value']);

        dsm($third_level);
  
        foreach ($third_level->text_2['und'] as $a_text_2) {
          print '<p>' . $a_text_2['safe_value'] . '</p>';
	    }
	    foreach ($third_level->image_2['und'] as $a_image_2) {
          $var = array(
            'item' => $a_image_2,
            'image_style' => 'medium',
            'path' => ''
          );
          print theme_image_formatter($var);
	    }
	  }
	}
    //and finally, for the above mentioned case, you can go back to the $top_level object and load that second level field collection #2.
    foreach ($top_level->second_level_collection_2['und'] as $a_second_level_collection_2) {
      $second_level_2 = load_collection($a_second_level_collection_2['value']);

      dsm($second_level_2);
  
      foreach ($second_level_2->text_3['und'] as $a_text_3) {
        print '<p>' . $a_text_3['safe_value'] . '</p>';
	  }
      foreach ($second_level_2->check_1['und'] as $a_check_1) {
        print '<p>Checklist value is: ' . $a_check_1['value'] . '</p>';
	  }
	  foreach ($second_level_2->image_3['und'] as $a_image_3) {
        $var = array(
          'item' => $a_image_2,
          'image_style' => 'thumbnail',
          'path' => ''
        );
        print theme_image_formatter($var);
	  }
	}
  }

  //close your custom wrapper.
  print '</div>' . "\n"; 
}
?>

PS I suspect that an issue queue probably shouldn't be used for posting code like this, but I didn't know where else to put it. I figured that someone wanting to know how to theme nested collections would look here, so it would be okay to post. Please accept my apologies for any inconvenience that this may have caused. I also know I started a previous thread about this, but that old thread had started dealing with other issues, so I thought it would be best to start a new one.

Comments

willhowlett’s picture

Really useful. Thanks loads for sharing.

edit: (I wonder whether this function would be useful to have in the field collection module itself)

edit 2: Regarding the actual output of the fields int he example above, it seems from this article (http://www.computerminds.co.uk/articles/rendering-drupal-7-fields-right-way) that using 'und' and 'safe_value' to identify them is the wrong way to do things. Haven't got my head round the article yet but will post anything useful I find.

edit 3: Ok, example code that I'm using, based on the theme function kindly posted above, and the advice in the article I linked to (as you can tell from my naming conventions I'm making a carousel):

<?php foreach ($content['field_carousel']['#items'] as $entity_uri) : ?>

<!-- using load_collection function to grab the entity data) -->
<?php $carousel_item = load_collection($entity_uri['value']);?>
			
<!-- using field_get_items to get an array of the entity data, without having to go through 'und' (which shouldn't be used as it's reserved for multi language functionality). Items from this array can be printed out seperately from the $carousel_image array if required (using check_plain / check_markup for user input as detailed here: http://drupal.org/node/28984) -->
<?php $carousel_image = field_get_items('field_collection_item', $carousel_item, 'field_image');?>
			
<!-- use field_view_value to create a renderable array from the array created above -->
<?php $carousel_image_output = field_view_value('field_collection_item', $carousel_item, 'field_image', $carousel_image[0]);?>
			
<!-- render the field --.
<?php print render($carousel_image_output);?>
			
		<?php endforeach;?>

Certainly seems a bit convoluted, and is more complicated than the accepted methods in Drupal 6, but is apparently correct (I think!).

cameron prince’s picture

Thank you for sharing this. It was very helpful.

letsbuild’s picture

This should be rolled into the module, trying to theme a field collection without it is a nightmare.

hey_germano’s picture

So glad I found this thread. Extremely handy function. Thanks for sharing!

AaronChristian’s picture

Just wanted to point out that the load_collection() function is no longer valid. You'll want to replace that with the: field_collection_field_get_entity() function.

For example;

<?php
$second_level = field_collection_field_get_entity($a_second_level_collection);
?>

*Note: You won't need to load the ['value'] part into the array.

AaronChristian’s picture

Any ideas on this, stuck with adding PHP to the themed field collection;

https://www.drupal.org/node/2314163

Shashwat Purav’s picture

Awesome documentation. Very helpful!