Classes For (Almost) Everything In A Drupal Theme

Thanks to the Zen theme there is an awesome bit of code available to Drupal themers which enables the addition of dynamic classes to your body tag based upon a number of different parameters, such as your position and status within a site, like so:

Drupal theme body classes

As you can imagine, this is incredibly useful for Drupal theming, as it provides a set of classes on the body which then allow a simple way to target specific areas of a theme via CSS.

I used to end up writing lots of different bits of code into a theme file to achieve this functionality, but the Zen theme does a great job of wrapping it all up in one bundle, and also nicely comments the code so that you can figure out what's happening :)

How to use the code:

Implementing the code into your own theme is a simple two step process:

  • Step 1.
    Copy the main chunk of code into your template.php file
  • Step 2.
    Edit your theme's page.tpl.php file to add the body classes

Step 1
The main chunk of code you will need lives in the Zen theme's ZENtemplate.php file.

I have edited it slightly to remove any Zen theme specific code (such as references to Zen theme sub-themes).

Copy all of this code (except the opening and closing php tags) and paste it into your theme's template.php file.

(Just be aware that if your theme's template.php file already contains a _phptemplate_variables() function you will need to integrate the new code with your existing code, as you cannot re-declare the same function. Unfortunately, it's difficult to give a more detailed explanation about how to do this as each situation will be different.)

<?php
/*
* CREATE OR MODIFY VARIABLES FOR YOUR THEME
*
* The most powerful function available to themers is _phptemplate_variables().
* It allows you to pass newly created variables to different template (tpl.php)
* files in your theme. Or even unset ones you don't want to use.
*
* It works by switching on the hook, or name of the theme function, such as:
*   - page
*   - node
*   - comment
*   - block
*
* By switching on this hook you can send different variables to page.tpl.php
* file, node.tpl.php (and any other derivative node template file, like
* node-forum.tpl.php), comment.tpl.php, and block.tpl.php.
*/


/**
* Intercept template variables
*
* @param $hook
*   The name of the theme function being executed (name of the .tpl.php file)
* @param $vars
*   A copy of the array containing the variables for the hook.
* @return
*   The array containing additional variables to merge with $vars.
*/
function _phptemplate_variables($hook, $vars = array()) {
 
// Get the currently logged in user
 
global $user, $theme_key;

 
// Set a new $is_admin variable. This is determined by looking at the
  // currently logged in user and seeing if they are in the role 'admin'. The
  // 'admin' role will need to have been created manually for this to work this
  // variable is available to all templates.
 
$vars['is_admin'] = in_array('admin', $user->roles);

 
// Send a new variable, $logged_in, to tell us if the current user is
  // logged in or out. An anonymous user has a user id of 0.
 
$vars['logged_in'] = ($user->uid > 0) ? TRUE : FALSE;

  switch (
$hook) {
    case
'page':
      global
$theme;

     
// Classes for body element. Allows advanced theming based on context
      // (home page, node of certain type, etc.)
     
$body_classes = array();
     
$body_classes[] = ($vars['is_front']) ? 'front' : 'not-front';
     
$body_classes[] = ($vars['logged_in']) ? 'logged-in' : 'not-logged-in';
      if (
$vars['node']->type) {
       
// If on an individual node page, put the node type in the body classes
       
$body_classes[] = 'node-type-'. $vars['node']->type;
      }
      if (
$vars['sidebar_left'] && $vars['sidebar_right']) {
       
$body_classes[] = 'two-sidebars';
      }
      elseif (
$vars['sidebar_left']) {
       
$body_classes[] = 'one-sidebar sidebar-left';
      }
      elseif (
$vars['sidebar_right']) {
       
$body_classes[] = 'one-sidebar sidebar-right';
      }
      else {
       
$body_classes[] = 'no-sidebars';
      }
      if (!
$vars['is_front']) {
       
// Add unique classes for each page and website section
       
$path = drupal_get_path_alias($_GET['q']);
        list(
$section,) = explode('/', $path, 2);
       
$body_classes[] = zen_id_safe('page-'. $path);
       
$body_classes[] = zen_id_safe('section-'. $section);
        if (
arg(0) == 'node') {
          if (
arg(1) == 'add') {
            if (
$section == 'node') {
             
array_pop($body_classes); // Remove 'section-node'
           
}
           
$body_classes[] = 'section-node-add'; // Add 'section-node-add'
         
}
          elseif (
is_numeric(arg(1)) && (arg(2) == 'edit' || arg(2) == 'delete')) {
            if (
$section == 'node') {
             
array_pop($body_classes); // Remove 'section-node'
           
}
           
$body_classes[] = 'section-node-'. arg(2); // Add 'section-node-edit' or 'section-node-delete'
         
}
        }
      }
     
$vars['body_classes'] = implode(' ', $body_classes); // Concatenate with spaces

     
break;

    case
'node':
     
// Special classes for nodes
     
$node_classes = array();
      if (
$vars['sticky']) {
       
$node_classes[] = 'sticky';
      }
      if (!
$vars['node']->status) {
       
$node_classes[] = 'node-unpublished';
       
$vars['unpublished'] = TRUE;
      }
      else {
       
$vars['unpublished'] = FALSE;
      }
      if (
$vars['node']->uid && $vars['node']->uid == $user->uid) {
       
// Node is authored by current user
       
$node_classes[] = 'node-mine';
      }
      if (
$vars['teaser']) {
       
// Node is displayed as teaser
       
$node_classes[] = 'node-teaser';
      }
     
// Class for node type: "node-type-page", "node-type-story", "node-type-my-custom-type", etc.
     
$node_classes[] = 'node-type-'. $vars['node']->type;
     
$vars['node_classes'] = implode(' ', $node_classes); // Concatenate with spaces

     
break;

    case
'comment':
     
// We load the node object that the current comment is attached to
     
$node = node_load($vars['comment']->nid);
     
// If the author of this comment is equal to the author of the node, we
      // set a variable so we can theme this comment uniquely.
     
$vars['author_comment'] = $vars['comment']->uid == $node->uid ? TRUE : FALSE;

     
$comment_classes = array();

     
// Odd/even handling
     
static $comment_odd = TRUE;
     
$comment_classes[] = $comment_odd ? 'odd' : 'even';
     
$comment_odd = !$comment_odd;

      if (
$vars['comment']->status == COMMENT_NOT_PUBLISHED) {
       
$comment_classes[] = 'comment-unpublished';
       
$vars['unpublished'] = TRUE;
      }
      else {
       
$vars['unpublished'] = FALSE;
      }
      if (
$vars['author_comment']) {
       
// Comment is by the node author
       
$comment_classes[] = 'comment-by-author';
      }
      if (
$vars['comment']->uid == 0) {
       
// Comment is by an anonymous user
       
$comment_classes[] = 'comment-by-anon';
      }
      if (
$user->uid && $vars['comment']->uid == $user->uid) {
       
// Comment was posted by current user
       
$comment_classes[] = 'comment-mine';
      }
     
$vars['comment_classes'] = implode(' ', $comment_classes);

     
// If comment subjects are disabled, don't display 'em
     
if (variable_get('comment_subject_field', 1) == 0) {
       
$vars['title'] = '';
      }

      break;
   
    case
'block':
     
$block = $vars['block'];

     
// Special classes for blocks
     
$block_classes = array();
     
$block_classes[] = 'block-'. $block->module;
     
$block_classes[] = 'region-'. $vars['block_zebra'];
     
$block_classes[] = $vars['zebra'];
     
$block_classes[] = 'region-count-'. $vars['block_id'];
     
$block_classes[] = 'count-'. $vars['id'];
     
$vars['block_classes'] = implode(' ', $block_classes);

      if (
theme_get_setting('zen_block_editing') && user_access('administer blocks')) {
       
// Display 'edit block' for custom blocks
       
if ($block->module == 'block') {
         
$edit_links[] = l('<span>'. t('edit block') .'</span>', 'admin/build/block/configure/'. $block->module .'/'. $block->delta, array('title' => t('edit the content of this block'), 'class' => 'block-edit'), drupal_get_destination(), NULL, FALSE, TRUE);
        }
       
// Display 'configure' for other blocks
       
else {
         
$edit_links[] = l('<span>'. t('configure') .'</span>', 'admin/build/block/configure/'. $block->module .'/'. $block->delta, array('title' => t('configure this block'), 'class' => 'block-config'), drupal_get_destination(), NULL, FALSE, TRUE);
        }

       
// Display 'administer views' for views blocks
       
if ($block->module == 'views' && user_access('administer views')) {
         
$edit_links[] = l('<span>'. t('edit view') .'</span>', 'admin/build/views/'. $block->delta .'/edit', array('title' => t('edit the view that defines this block'), 'class' => 'block-edit-view'), drupal_get_destination(), 'edit-block', FALSE, TRUE);
        }
       
// Display 'edit menu' for menu blocks
       
elseif (($block->module == 'menu' || ($block->module == 'user' && $block->delta == 1)) && user_access('administer menu')) {
         
$edit_links[] = l('<span>'. t('edit menu') .'</span>', 'admin/build/menu', array('title' => t('edit the menu that defines this block'), 'class' => 'block-edit-menu'), drupal_get_destination(), NULL, FALSE, TRUE);
        }
       
$vars['edit_links_array'] = $edit_links;
       
$vars['edit_links'] = '<div class="edit">'. implode(' ', $edit_links) .'</div>';
      }

      break;
  }

  return
$vars;
}

/**
* Converts a string to a suitable html ID attribute.
*
* - Preceeds initial numeric with 'n' character.
* - Replaces any character except A-Z, numbers, and underscores with dashes.
* - Converts entire string to lowercase.
* - Works for classes too!
*
* @param $string
*   The string
* @return
*   The converted string
*/
function zen_id_safe($string) {
  if (
is_numeric($string{0})) {
   
// If the first character is numeric, add 'n' in front
   
$string = 'n'. $string;
  }
  return
strtolower(preg_replace('/[^a-zA-Z0-9_-]+/', '-', $string));
}

?>

Step 2
Now open you theme's page.tpl.php file.

Most likely, the opening body tag will just look like so:

<body>

Change this to:

<body class="<?php print $body_classes; ?>">

and that's it.

If you now look at the source code of your site you should see a series of classes attached to the body tag ready to be utilised in your theme.

For further information about the Zen theme you should check out this Lullabot podcast where Jeff Robbins (the theme's creator) and John Wilkins (the current maintainer) discuss the theme in more detail.

6 comments

Josiah April 9th, 2008 @ 3:43pm

Thanks for this. I've been meaning to dig into the Zen theme (and drupal themeing in general). This should give me a leg up to figuring out what needs to happen.

gois April 9th, 2008 @ 4:53pm

I haven't had time to read all about Drupal but all seems to really
interesting i just want to congratulate all the developeres.

Frando April 9th, 2008 @ 5:23pm

Good news: A major part of this code made it into Drupal 6 core! So in Drupal 6, you can simply use $body_classes in your theme without any additional code. And even better: For CSS-only themes without any template files (this is possible in Drupal 6), the body classes are included in the default markup.

See http://drupal.org/node/163723 for details.

Tj Holowaychuk April 22nd, 2008 @ 4:33pm

Kind of dumb, just use the themer module its much easier

http://drupal.org/project/themer

jeff September 29th, 2008 @ 7:28pm

The code now included in Drupal 6 core doesn't seem to do the section classes (which is what I needed). I got it working by using this code from a newer version of the zen theme:

<?php
function phptemplate_preprocess_page(&$vars, $hook) {
 
 
// Classes for body element. Allows advanced theming based on context
  // (home page, node of certain type, etc.)
 
$body_classes = array($vars['body_classes']);
  if (!
$vars['is_front']) {
   
// Add unique classes for each page and website section
   
$path = drupal_get_path_alias($_GET['q']);
    list(
$section, ) = explode('/', $path, 2);
   
$body_classes[] = zen_id_safe('page-' . $path);
   
$body_classes[] = zen_id_safe('section-' . $section);
    if (
arg(0) == 'node') {
      if (
arg(1) == 'add') {
        if (
$section == 'node') {
         
array_pop($body_classes); // Remove 'section-node'
       
}
       
$body_classes[] = 'section-node-add'; // Add 'section-node-add'
     
}
      elseif (
is_numeric(arg(1)) && (arg(2) == 'edit' || arg(2) == 'delete')) {
        if (
$section == 'node') {
         
array_pop($body_classes); // Remove 'section-node'
       
}
       
$body_classes[] = 'section-node-' . arg(2); // Add 'section-node-edit' or 'section-node-delete'
     
}
    }
  }
 
$vars['body_classes'] = implode(' ', $body_classes); // Concatenate with spaces 
}

/**
* Converts a string to a suitable html ID attribute.
*
* - Preceeds initial numeric with 'n' character.
* - Replaces any character except A-Z, numbers, and underscores with dashes.
* - Converts entire string to lowercase.
* - Works for classes too!
*
* @param $string
*   The string
* @return
*   The converted string
*/
function zen_id_safe($string) {
  if (
is_numeric($string{0})) {
   
// If the first character is numeric, add 'n' in front
   
$string = 'n'. $string;
  }
  return
strtolower(preg_replace('/[^a-zA-Z0-9_-]+/', '-', $string));
}
?>
akahn April 16th, 2009 @ 2:11pm

You missed one mention of Zen: in the page case, there are calls to zen_id_safe(), which won't be defined in a custom theme.

Comments are closed

If you want to ask a question or have something to add please contact me.