Advanced FlexSlider Construction in Drupal 7

Ski slide

Flex Slider is a popular Drupal module used to create responsive sliders with the FlexSlider 2 jQuery plugin. It's powerful and flexible with good hooks available for customization. It's not always clear, though, how to construct sliders in more complex situations.

The Challenge

Our example scenario consists of two content types. The first, call it Roadkill, has two fields of interest: a multivalue image field and a standard body field. The second, Cuisine, has a multivalue field collection field (field_cuisine_slider). The field collection field consists of a reference to a Roadkill node plus a single value image field.

The challenge is to create a slider with the following characteristics:

  • The main slide consists of the field collection image field plus the body text of the referenced node.
  • The slider uses thumbnail navigation using the first image of each referenced node.

(Note: this scenario may appear a little contrived, but it's actually based on a real case in which one group of users could create and edit the first content type and another group of users could create and edit the second content type.)

Flexislider graphic

Site Building

The amount of extra site building is actually pretty minimal. First, we need a couple of modules (other than Flex Slider and the modules used to setup the content type architecture):

Next, create a new view mode. In Display Suite, you can do this by going to Admin→Structure→Display Suite→View modes→Add a view mode. Give it a name, like Cuisine Slider (machine name cuisine_slider) and enable it at least for field collection entities.

In the field collection (Admin→Structure→Field collections→your field collection→manage display→Custom display settings), enable the new view mode for this field collection. Save the field collection, then configure the view mode so that it contains just the image. This will be the main slide image. We'll add the text later in code. Save the field collection again.

(Note: you could instead add text from the Roadkill node here using the Rendered entity field formatter and appropriate configuration on the Roadkill node, but we're doing it this way to look at a particular facet of Flex Slider.) 

Now, add a FlexSlider option set in Admin→Configuration→Media→FlexSlider→Add. Give it a name, like Cuisine Slider (machine name cuisine_slider) and on the Navigation and Control Settings vertical tab, set Paging Controls to Thumbnails. You might also uncheck Slideshow on the General Slideshow and Animation Settings tab so that the slider doesn't rotate automatically, but that's up to you. Save the option set.

In the Cuisine content type, manage the display where you are going to use the slider (Admin→Structure→Content types→your content type→Manage display). For the field collection field, set the Format to FlexSlider. Choose the Option Set you just created (Cuisine Slider), and the view mode you created (Cuisine Slider). Change the number of values to display or skip if you want. Click Update and then save the content type.

That's all the site building that's necessary. Just four things:

  1. Create a view mode
  2. Configure the field collection to use the view mode
  3. Create a FlexSlider option set
  4. Configure the content type to tie it together.

Write Some Hooks

FlexSlider defines several useful hooks, but we only need one of them plus one other core hook.

In your theme's template.php file, write an implementation of template_preprocess_field. In this hook, we:

  1. Extract the field collection fields we need
  2. (This is key) Put them in a place accessible to the other hook we're implementing.

You can use any method you want to get the field collection fields in an easier to manage state; I just used the function from this Four Kitchens post verbatim.

/**
 * Implements template_preprocess_field
 */
function mytheme_preprocess_field(&$vars, $hook) {

  if ($vars['element']['#field_name'] == 'field_cuisine_slider') {

    $field_array = array('field_roadkill', 'field_main_slide_image');
    _rows_from_field_collection($vars, 'field_cuisine_slider', $field_array);

    // Copy the rows into the item array so they will get passed to the flexslider list preprocessor.
    // Do this here to keep the row processor generic.
    foreach ($vars['items'][0]['#items'] as $index => $item) {
      $vars['items'][0]['#items'][$index]['row'] = $vars['rows'][$index];
    }
  }
}

/**
 * Creates a simple text rows array from a field collections, to be used in a
 * field_preprocess function.
 *
 * @param $vars
 *   An array of variables to pass to the theme template.
 *
 * @param $field_name
 *   The name of the field being altered.
 *
 * @param $field_array
 *   Array of fields to be turned into rows in the field collection.
 */

function _rows_from_field_collection(&$vars, $field_name, $field_array) {
  $vars['rows'] = array();
  foreach($vars['element']['#items'] as $key => $item) {
    $entity_id = $item['value'];
    $entity = field_collection_item_load($entity_id);
    $wrapper = entity_metadata_wrapper('field_collection_item', $entity);
    $row = array();
    foreach($field_array as $field){
      $row[$field] = $wrapper->$field->value();
    }
    $vars['rows'][] = $row;
  }
}

Next, write an implementation of template_preprocess_flexslider_list (see flexslider.theme.inc that is part of the FlexSlider module). In this hook, we're doing a few things for each slide:

  1. Retrieve the values from the referenced Roadkill node we need for the slider.
  2. Set the $items[$index]['title'] element to the body of the Roadkill node. The FlexSlider theme function wraps this in a <p class="flex-caption"> tag, so you might want to do some processing on the text to ensure there are no disallowed tags (like <div>).
  3. Set the $items[$index]['thumb'] element to the first image of the referenced Roadkill node.
/**
 * Implements template_preprocess_flexslider_list
 */
function mytheme_preprocess_flexslider_list(&$vars) {

  // Only process the optionset we defined
  if (isset($vars['settings']['optionset']->name == 'cuisine_slider') {

    $items = &$vars['items'];
    foreach ($items as $index => $item) {

      // Use entity_metadata_wrapper for more standard access
      $roadkill = entity_metadata_wrapper('node', $item['row']['field_roadkill']);

      // The thumbnail is the first image in the referenced roadkill
      $thumb = $roadkill->field_image[0]->value();
      $thumb_style = $vars['settings']['optionset']->imagestyle_thumbnail;
      $items[$index]['thumb'] = image_style_url($thumb_style, $thumb['uri']);

      // The caption is the body of the referenced roadkill. The 'title' index becomes the 'caption' 
      // index in flexslider list theme functions.
      $items[$index]['title'] = $roadkill->body->value->value(array('sanitize' => TRUE));
    }
  }
}

Move Along

In this example, we've taken a relatively complex challenge and reduced it to a few configuration steps and a couple of hooks. All that's left is the theming.