HomeDrupalDrupal Field Collections: Repeat with Style and Class

Drupal Field Collections: Repeat with Style and Class

Content comes in many shapes, forms and sizes. However, there are some common content patterns that we web designers and developers use quite often. One of these patterns is the combination of image, title, text and link to create a card-style teaser—often used, often created, often duplicated—and for good reason.

Let’s take a look at how Drupal may be used to create and display teaser content patterns to give content editors more display options. A combination of field collections, preprocess functions and CSS provides everything we need to create a variety of patterns based on the number of times the field collection is repeated. For example, let’s say you have a field collection that’s designed to create teaser content that consists of an image, title, text description and a link to more content. If you are displaying only one instance of the field collection, it may look something like this:

However, if you have three instances of that same field collection, you may want it to look something more like this:

Creating the layout for each of these display patterns is accomplished fairly easily as long as you know the total number of field collections that will be populated and displayed. However, it gets a little trickier when the number of instances is not defined or set in concrete—it might be one, four or six. Each of these variations might require a different layout similar to the examples shown above.

The good news is we can create these variations relatively easily using flexbox. The bad news? Flexbox may not solve every problem. If the layout within each teaser needs to vary depending on the total number of teasers, or if your required browsers do not support flexbox, then you will need a class that keys off the total number of field collection instances. By default field collection does not give you the number of instances via a class. Depending on your theme, the markup looks something like this:


<div class="field field-name-field-fc-teaser field-type-field-collection field-label-hidden">
  <div class="field-items">
    <div class="field-item even">
      <div class="entity entity-field-collection-item field-collection-item-field-fc-teaser clearfix">
        <div class="content">

        ...

        </div>
      </div>
    </div>
  </div>
</div>

This gives us plenty of structure and classes to theme any variation where we already know the number of field-item containers, but it does not give us the number of instances that this field collection is repeated and displayed on the front end. We need that information  for our CSS to create layouts that will accommodate our possible number of instances. This will determine if each field-item is 100 percent, 33 percent, 50 percent, or whatever width works for the layout. It also can be used to affect the layout within each teaser. For example, if there is only one teaser, the position of the image has many options; with more than two teasers, the image could appear above the text. There are numerous variations for rearranging the display of this content depending on the number of teasers. What we need here to fully control our layout options is an additional class on the outer most div that tells us how many instances we have. Something along the line field-collections-1 or field-collections-3. For example:


<div class="field field-name-field-fc-teaser field-type-field-collection field-label-hidden field-collections-3">

Luckily a simple preprocess function and some not-too-difficult programming will get us the classes we need. The template_preprocess_field() function gives us access to all the data we need to get the number of items and add a class based on that value. Our field-level preprocess function will end up looking something like this:


/**
 * Implements hook_preprocess_field(&$variables)
 */
function nameoftheme_preprocess_field(&$variables) {
  // Limit this to the desired field.
  if($variables['element']['#field_name'] == 'name_of_fieldcollection') {
    // Get number of field collections.
    $field_collection_count = count($variables['items']);
    // Add class based on number of field collections.
    switch ($field_collection_count) {
      case 1:
        $variables['classes_array'][] = 'field-collections-1';
        break;
      case 2:
        $variables['classes_array'][] = 'field-collections-2';
        break;
      case 3:
        $variables['classes_array'][] = 'field-collections-3';
        break;
    }
  }
}

If Drupal preprocess functions are familiar to you, then this is fairly straightforward and probably requires no further explanation. However if this is a new concept, it’s still pretty simple: 

We basically need to perform three steps:

  1. Isolate the field we are preprocessing
  2. Get the data we need from that field (total number of field collections)
  3. Add the desired classes based on that number

Isolate the Field

Since the template_preprocess_field() function will affect all fields on our site, we want to take steps to ensure we are only affecting this specific field. There are a couple different ways to accomplish this. In this case, I used devel and the dpm() function to inspect the data stored in the array for this field and used an if statement and the value stored in $variables[‘element’][‘#field_name’] to make sure we only target this specific field collection.


/**
 * Implements hook_preprocess_field(&$variables)
 */
function nameoftheme_preprocess_field(&$variables) {
  // Limit this to the desired field.
  if($variables['element']['#field_name'] == 'name_of_fieldcollection') {
...

Get the Data

Now we can inspect and access the data stored in $variables[‘items’] to get the total number of field collection instances. The PHP count() function helps us access the data stored in the $variables[‘items’] array. We store that data in our $field_collection_count variable, which we will use in the next step.


/**
 * Implements hook_preprocess_field(&$variables)
 */
function front_preprocess_field(&$variables) {
  // Limit this to the desired field.
  if($variables['element']['#field_name'] == 'field_fc_teaser') {
    // Get number of field collections.
    $field_collection_count = count($variables['items']);

    ...

Add Desired Classes

Now that we have the total number of field collection instances stored in the $field_collection_count variable, we can create some classes based on the number of fields. In this example I have set the field collection to a maximum of three instances, so I know only three instances total need to be accounted for. The $variables[‘classes_array’] stores the default classes available to this field collection and is also the appropriate place to add our new classes. Using if else statements or a switch statement, we can key off the number of field collection items stored in the $field_collection_count variable and assign it to a new value in the $variables[‘classes_array’] array.


/**
 * Implements hook_preprocess_field(&$variables)
 */
function front_preprocess_field(&$variables) {
  // Limit this to the desired field.
  if($variables['element']['#field_name'] == 'field_fc_teaser') {
    // Get number of field collections.
    $field_collection_count = count($variables['items']);
    // Add class based on number of field collections.
    switch ($field_collection_count) {
      case 1:
        $variables['classes_array'][] = 'field-collections-1';
        break;
      case 2:
        $variables['classes_array'][] = 'field-collections-2';
        break;
      case 3:
        $variables['classes_array'][] = 'field-collections-3';
        break;
    }
  }
}

Our rendered HTML now has the class we need and looks something like this:


<div class="field field-name-field-fc-teaser field-type-field-collection field-label-hidden field-collections-3">
  <div class="field-items">
    <div class="field-item even">
      <div class="entity entity-field-collection-item field-collection-item-field-fc-teaser clearfix">
        <div class="content">
        ...
        </div>
      </div>
    </div>
  </div>
</div>

This gives all the markup and classes we need to pull off a variety of layouts that can differ depending on the total number of field collections.

Thanks to my co-worker, Nate Hero, a back-end developer, for helping me come up with a simple solution. I hope it helps you get the classes you need. The CSS is up to you, my friend.

Does your content fit into one simple box? If not, NEWMEDIA can help. Contact us if you have a variety of content that does not fit into a one size fits all solution.