A sort of "swiss army knife" when theming a node is to have a CSS class added to it for each flag set. So, for example, if the node is bookmarked, it will also have a "node-flagged-bookmarks" class. And if it's flagged as funny, it will also have a "node-flagged-funny" class.

2010-09-29: The Flag Classes module will be released soon. You won't need to use the recipes in this page anymore.

The code

To make this happen, put the following preprocess function in your 'template.php'. If you already have a function by the same name, change 'phptemplate' to the name of your theme (or replace it with the name of any module you have enabled). Don't forget to clear Drupal's cache or else this new code won't be picked up.

An added bonus: if a flag is set by the current user, a "node-flagged-FLAGNAME-self" class will be generated as well. For example, "node-flagged-funny" will be generated if anyone found the joke (the node) to be funny. But "node-flagged-funny-self" will be generated only if you found it as such.

/**
 * Generates a node-flagged-FLAGNAME css class for every flag set on the node.
 *
 * In addition, if the flag is set by the current user, a
 * node-flagged-FLAGNAME-self class will be generated as well.
 *
 * The class names are accumulated in the $flag_classes variable and you must 
 * use it in your 'node.tpl.php', or else it will have no effect.
 */
function phptemplate_preprocess_node(&$vars) {
  $vars['flag_classes'] = implode(' ', _phptemplate_get_flag_classes('node', $vars['node']->nid));
}

/**
 * Returns a list of CSS classes for a flagged item.
 *
 * The classes are of the form "node-flagged-FLAGNAME" (where "node" would be
 * "user" or "comment": depending on the item type).
 *
 * Additionally, if the flag is set by the current user, a
 * "node-flagged-FLAGNAME-self" class will be generated as well.
 *
 * @param $content_type
 *   The item type: Either "node" or "user" or "comment" or whatever.
 * @param $content_id
 *   The item ID.
 */
function _phptemplate_get_flag_classes($content_type, $content_id) {
  static $flags = array();

  if (!module_exists('flag')) {
    return array();
  }

  if (!isset($flags[$content_type])) {
    $flags[$content_type] = flag_get_flags($content_type);
  }

  // Note: is_flagged() and get_count() use internal cache,
  // so using them won't result in issuing excessive SQL queries.

  $classes = array();
  foreach ($flags[$content_type] as $flag) {
    $css_name = str_replace('_', '-', $flag->name);
    if (!$flag->global && $flag->is_flagged($content_id)) {
      // The item is flagged by me.
      $classes[] = $content_type . '-flagged-' . $css_name;
      $classes[] = $content_type . '-flagged-' . $css_name . '-self';
    } elseif ($flag->get_count($content_id)) {
      // The item is flagged by anybody.
      $classes[] = $content_type . '-flagged-' . $css_name;
    }
  }
  return $classes;
}

The class names are accumulated in the $flag_classes varibale. You should embed it in your 'node.tpl.php'. For example:

<div id="node-..." class="<?php echo $flag_classes; ?> ...

(For Drupal 5 instructions see older revisions of this page.)

Testing the code

To test the code add the following to your stylesheet:

/* Change "bookmarks" to the machine-name of the flag you're intereseted in. */

/* If this is a global flag, or you're interested in other people's flagging,
   remove the "-self" */

.node-flagged-bookmarks-self {
  background-color: yellow !important;
}

Flagged nodes should be painted in yellow.

Dynamically updating the CSS

Our code works, but it has one problem: when we flag items using the JavaScript link, the CSS classes aren't updated. This isn't surprising because the page isn't refreshed. Fortunately, it's easy to dynamically update the CSS classes using JavaScript. Use the following code:

(function ($) {

// Update the CSS classes of flagged items after their flag link is clicked.
$(document).bind('flagGlobalAfterLinkUpdate', function(event, data) {
  var class_names = data.contentType + '-flagged-' + data.flagName.replace(/_/g, '-');
  class_names += ' ' + class_names + '-self';
  var method = (data.flagStatus == 'flagged') ? 'addClass' : 'removeClass';
  $(data.link).parents('div.node, .views-row').eq(0)[method](class_names);
});

})(jQuery);

Put this code in a "flag-css.js" file (for example) in you theme's folder and add "scripts[] = flag-css.js" to your theme's .info file. Then clear Drupal's cache or else Drupal's theming system won't notice this new file.

What about Views?

When your view is styled as a table, list or unformatted, then 'node.tpl.php' isn't used and therefore the rows in the view won't carry our flag classes. To fix this, add the following preprocess functions to your template.php. Rename garland to the actual name of your theme. Don't forget to clear Drupal's cache.

#
# You must rename 'garland' to the actual name of your theme !!
#

function garland_preprocess_views_view_table(&$vars) {
  $view = $vars['view'];
  $rows = $vars['rows'];

  if ($view->base_table == 'node') {
    foreach ($rows as $id => $row) {
      $data = $view->result[$id];
      $flag_classes = implode(' ', _phptemplate_get_flag_classes('node', $data->nid));
      $vars['row_classes'][$id][] = $flag_classes;
      $vars['row_classes'][$id][] = 'views-row'; // For our JavaScript.
    }
  }
}

function garland_preprocess_views_view_unformatted(&$vars) {
  $view = $vars['view'];
  $rows = $vars['rows'];

  if ($view->base_table == 'node'
       && $view->display_handler->get_option('row_plugin') != 'node'
       && empty($view->style_plugin->options['grouping'])) {  // Note: we don't support Grouping.
    foreach ($rows as $id => $row) {
      $data = $view->result[$id];
      $flag_classes = implode(' ', _phptemplate_get_flag_classes('node', $data->nid));
      $vars['classes'][$id] .= ' ' . $flag_classes;
    }
  }
}

function garland_preprocess_views_view_list(&$vars) {
  garland_preprocess_views_view_unformatted($vars);
}

Note: when using the list or unformatted styles, the classes won't be added to the rows if you use Grouping in your view.

(This code was tested on both Views 2.x and Views 3.x.)

This code is complicated! Why don't you just put it in a module?

Indeed, the ideal place for this code is in a contrib module.

Comments

kaerast’s picture

This appears not to work when the node is overridden by a panel. Node.tpl.php seems not to be used, and the node data loaded directly by the panels module.

kepford’s picture

How would this work with a User Flag on the Profile page? I have it working with node flags.

Bob Kepford
TheWeeklyDrop - A Weekly Drupal Newsletter

OxH2’s picture

Thanks for this excellent recipe. I'm using Views and I'd like to apply the flagged background color class to a div that is subordinate to the 'views-row' div.

Is there an easy way to access the class data from inside views-view-fields.tpl.php?

The classes are applied successfully in the line:

<div class="<?php print $classes; ?>">

of views-view.tpl.php. however I'm having trouble accessing the class data from inside views-view-fields.tpl.php.

I have been able to adjust the javascript to apply the class to the div that I'm interested in by changing .view-row to my desired div in

$(data.link).parents('div.node, .views-row').eq(0)[method](class_names);

However this only works for divs that a user has flagged since they loaded the page and doesn't reflect past flaggings.

Can I achieve this by setting up another variable in my template.php following the line that adjusts the row classes?

$vars['classes'][$id] .= ' ' . $flag_classes;

I've searched for an explanation on how to setup another variable (keyed to $id) that views-view-fields.tpl.php can access but so far I haven't been able make this happen.

Thanks for your time.

calte’s picture

Anyone have this working on Drupal 7?

kmare’s picture

I'm looking for a drupal 7 port as well.. anyone had any success?

bryan.cribbs’s picture

@kmare

For Drupal 7, I am only using the views integration, so I am uncertain on the rest of it, however I was able to get the classname added to my view by tweaking the hook_preprocess_views_view_unformatted() code listed in the current article.

At the end of that hook, I removed:

    $vars['classes'][$id] .= ' ' . $flag_classes;

And replaced it with:

      if ($flag_classes) 
        $vars['classes_array'][$id] .= " $flag_classes";

Like I said, I'm not using the rest of it, so ymmv.
-Bryan

kmare’s picture

Thanx Bryan,
actually I was trying to get it to work with views with a fullcalendar display. Unfortunately i didn't get it to work yet and lately I don't have much time, but if I manage to get to work on it again, I'll release a patch.

dgitonga’s picture

If you are using views calc table and want to see the highlighted rows, add the following preprocess function to your template.php. Don't forget to rename garland to your theme name.

function garland_preprocess_views_calc_table(&$vars) {
  $view = $vars['view'];
  $rows = $vars['rows'];

  if ($view->base_table == 'node') {
    foreach ($rows as $id => $row) {
      $data = $view->result[$id];
      $flag_classes = implode(' ', _phptemplate_get_flag_classes('node', $data->nid));
      $vars['row_classes'][$id][] = ' '.$flag_classes;
      $vars['row_classes'][$id][] = 'views-row'; // For our JavaScript.
    }
  }
}

Then copy the template file, views-calc-table.tpl.php, from the views calc module folder to your theme folder and make the following adjustment:
Find this line:
<tr class="<?php print ($count % 2 == 0) ? 'even' : 'odd'; ?>">
and replace it with:
<tr class="<?php print ($count % 2 == 0) ? 'even' : 'odd'; print implode(' ', $row_classes[$count]);?>">