A project we’re working on requires responsive lists of content comprised of only teaser images and titles. For such lists to look decent, the elements need visual consistency by virtue of being the same height, though the titles may vary greatly in length and in a responsive setting we don’t know the viewport size and how many items will be in a given row. Also, in our case the content won’t always have an existing image for us to work with.

So there are two main challenges:

  1. Generate a default image that can be tracked/versioned in some way, but also remain editable by site editors
  2. Ensure all list items are an equal height, regardless of title length and number of items in a row

Generate a default image in a managable way

We can use Drupal’s core image styles to format images from our content in a way that fits our grid system. For entities with an image, that’s all we need. For entities with no image, we need to perform a few steps to get the default image in place.

First, we copy the versioned file over to our public files space:

/*
 * Make sure there's a copy of the theme default node image in the public files
 * directory.
 *
 * @return TRUE if the image already exists or was created successfully, FALSE
 * otherwise.
 */
function _mytheme_create_default_theme_node_image() {
  // Get the path for public files.
  if ($wrapper = file_stream_wrapper_get_instance_by_uri('public://')) {
    $public_files_path = $wrapper->realpath();
  }
 
  $files_image_path = $public_files_path.'/default.png';
 
  // Copy the file to the public files path if it doesn't already exist.
  if (file_exists($files_image_path)) {
    return TRUE;
  }
  else {
    $theme_image_path = drupal_get_path('theme', 'mytheme').'/img/default.png';
    if (copy ($theme_image_path, $files_image_path)) {
      return TRUE;
    }
  }
 
  return FALSE;
}

Next, we generate the image style derivative we need for our content list:

/*
 * Make sure there's a derivative of the theme default node image for the given
 * image style.
 *
 * @param $style_name
 *  String representing the image style.
 *
 * @return
 *  String image path if the derivative exists or was successfully created,
 *  otherwise FALSE.
 */
function _mytheme_create_theme_node_image_derivative($style_name) {
  $image_path = 'public://default.png';
 
  $style_path = image_style_path($style_name, $image_path);
 
  if (file_exists($style_path)) {
    return $style_path;
  }
  else {
    $style = image_style_load($style_name);
    if (image_style_create_derivative($style, $image_path, $style_path)) {
      return $style_path;
    }
  }
 
  return FALSE;
}

We can call these functions from the function that’s handling our image field – theme_field or some custom function:

/*
 * Get the Main Image field content for return to the template for teasers.
 */
function _mytheme_preprocess_main_image_teaser($node) {
  $main_image = '';
  $style_name = 'teaser_thumbnail';
 
  // If we do have an image for this node, use that. Otherwise, use our default image.
 
  if ($image = field_get_items('node', $node, 'field_main_image')) {
    $image_markup = render(field_view_value('node', $node, 'field_main_image', $image[0], array(
      'type' => 'image',
      'settings' => array(
        'image_style' => 'teaser_thumbnail',
      ),
    )));
  }
  else {
    // If there's no Main Image for this node, print a default image from the 
    // theme.
 
    // First, make sure there's a copy of the source image in the public files
    // directory.
    _mytheme_create_default_theme_node_image();
 
    // Next, make sure the default theme image's appropriate derivative for 
    // this image style exists.
    $style_path = _mytheme_create_theme_node_image_derivative($style_name);
 
    // Finally, generate the image markup.
    $default_image = array(
      'style_name' => $style_name,
      'path' => $style_path,
    );
    $image_markup = theme('image_style', $default_image);
  }
 
  $main_image .= l($image_markup,'node/'.$node->nid,array('html'=>TRUE));
 
  return $main_image;
}

And should we ever want to change the default image, we can update the file in our version control system, delete the copy in the public files space, and flush the image style (e.g. with drush image-flush).

Ensure list elements are an equal height, regardless of viewport width

Getting the content list elements to be the same height is even simpler, thanks to Tim Svensen’s equalize.js jQuery plugin. To implement it, we just download and add the script to our repo, include it in our theme, and initialize it on the parent element of our content list.

Initializing the script is just a matter of finding the right selector on the page for our view, and wrapping one line of javascript in the Drupal Behaviors syntax:

/*
 * Initialize equalize.js on our desired elements.
 */
(function ($) {
  Drupal.behaviors.equalHeights = {
    attach: function(context, settings) {
      // Set equal heights on our view listings.
      $('div.view-display-id-page div.view-content').equalize();
    }
  };
})(jQuery);

And of course we make our theme aware of the script with a couple of lines in our .info file:

; Downloaded script.
scripts[] = js/equalize.min.js
; Our initialization script.
scripts[] = js/equalize.init.js

That’s it! We now have clean teaser lists with images and titles that are equal height regardless of the title lengths and number of items in a row. A default image is supplied if the content doesn’t have one, and we can easily change that default image in the future as needed.