Brutally Removing Extra Divs With Preprocess Suggestions

  • A .tpl.php template in Drupal may look clean, but when Drupal prints it out, for every field there's a div each for 'field', 'field-items', and 'field-item'. This is great for consistency: the same CSS will apply if it is a single-value field or one with fifty items. It's not so great if we want items from several different fields in-line or if we value clean, semantic markup. In an approach directly analogous to providing theme hook suggestions in hook_preprocess_node(), you can provide theme hook suggestions for fields by implementing hook_preprocess_field().

    As always, you can start by looking at the core model, template_preprocess_field(). There we can see what theme hook suggestions it already provides, and we can look at the code to see how it does it.

    Next, you can implement hook_preprocess_field() but do nothing for now except print out what variables you will have available:

    <?php
    function dgd7glue_preprocess_field(&$vars) {
     
    kpr($vars);
    }
    ?>

    We learn that in #element it knows the view mode! This means we can offer theme hook suggestions based on the view mode, and so use a different function when we want to.

    The function you will replace is theme_field().

    Method 1: Override a Theming Function in a Theme

    The below will override the field in the most specific way it can given the provided theme hooks, for a field with a given name (field_number, here) and node type (book).

    <?php
    /**
     * Overrides theme_field() for field numbers on books.
     *
     * @TODO- compact mode only.
     */
    function apress_field__field_number__book($variables) {
      return
    drupal_render($variables['items'][0]);
    }
    ?>

    If we want to do this for the compact build mode only, we need to use a preprocess function to add a theme hook suggestion. This can be done with the following (untested) code:

    <?php
    /**
     * Implements hook_preprocess_HOOK() for fields to add suggestions based on build mode .
     */
    function apress_preprocess_field($vars) {
     
    $vars['theme_hook_suggestions'][] = 'field__' . $vars['element']['#field_name'] . '__' . $vars['element']['#bundle'] . '__' . $vars['element']['#view_mode'];
    }
    ?>

    We would then change the name of the function above to 'apress_field__field_number__book__compact' to take advantage of the view mode level of specificity. (Note that you could put view mode earlier in the suggestion or swap in field type and make all manner of suggestions-- the most recently added suggestion that has an existing implementation, wins.)

    I like to stick the stuff that is tied to configuration or that i'll probably want no matter what the theme in a module, so that slightly more complicated method is covered next.

    Method 2: Replace a Theming Function from a Module

    While modules get in on the preprocess hook action, the do not get asked to override theme functions- unless we use define our own theme_hook in hook_theme() and use our chance in preprocess to add it specifically to the theme hook suggestions. We can take over the definition of the theme function completely in three steps.

    <?php
    /**
     * Implements hook_theme().
     */
    function dgd7glue_theme() {
      return array(
       
    'dgd7glue_field_single_item_plain' => array(
         
    'render element' => 'element',
         
    'function' => 'dgd7glue_field_single_item_plain',
        ),
      );
    }

    /**
     * Implements hook_preprocess_field().
     */
    function dgd7glue_preprocess_field(&$vars) {
      if (
    $vars['element']['#field_name'] == 'field_number'
         
    && $vars['element']['#bundle'] == 'book') {
    // Other code for a different purpose removed for clarity. If we did not
    // already have other code we would combine this into one if statement.
       
    if ($vars['element']['#view_mode'] == 'compact') {
         
    $vars['theme_hook_suggestion'] = 'dgd7glue_field_single_item_plain';
        }
      }
    }

    /**
     * Replaces theme_field() for removing all markup around single-item fields.
     */
    function dgd7glue_field_single_item_plain($vars) {
      return
    drupal_render($vars['items'][0]);
    }
    ?>