Tutorial: Different icons for Fivestar CCK fields

Requirements: basic css, image editing, basic theming

The Fivestar module provides a tidy little voting widget that allows users to vote on nodes. It also provides a CCK field of the type “Fivestar Rating”, which can be used to rate a node on multiple criteria.

One of my pet projects, Hill Bomb, requires just this functionality. It's a maps-mashup site for downhill riders, for example skateboarders or mountain bikers, to upload details and maps of awesome hills around the world. Here are the some of the ratings that users can give to hills:

Ratings

As one of the beta testers pointed out to me, it's difficult to judge what “more flames” means in each individual context. For example, does having more flames mean a better road surface, or more danger to the rider?

It would be better to have different image sets for each rating that clearly indicates what more and less actually means. Fivestar module doesn't provide this functionality out of the box - you can only choose one set of icons for all your Fivestar fields. So, I had to do a bit of modification to get it working.

Here's a tutorial with the examples from Hill Bomb. I only add one different image set here, but it can easily be extended for multiple.

Step 1: The images

Create the icon sets for each field. I chose small cars for the traffic fields. It's hopefully obvious that more cars = more traffic!

Cars

Making this image was easy in the fantastic open-source image manipulation program Paint.NET. It's just a matter of finding a cool icon, extending it's canvas to be 3 times it's original height, then duplicating it twice, and finally editing each one as preferred. The top image is the deactivated state, the second is the activated state, and the third is the roll-over state. Here I just left the roll-over to be identical to the activated state.

I also made this two-state “delete vote” image, the bottom one being the roll-over.

Delete Cars

Step 2: The custom widget

Next, go to sites/all/modules/fivestar/widgets, where you'll find a directory for each available widget set. Create a new directory that will contain your new image set. In this case, I called it hillbomb, after the site.

Now copy the contents of another widget directory, that will act as the base or fallback icons. These will be displayed by default, except where you specify your new icon set to be shown. I chose the flames icons, and copied over the three files (two sprite images and a .css). I renamed the .css from flames.css to the name of the folder, hillbomb.css.

Also copy in your new images that were made in step 1.

Step 3: The images in the css

Edit the css file in your new widget directory (in my example, hillbomb.css). Select everything, and copy-paste a duplicate of it at the end of the file. The original section should be left alone - this will ensure that the base icons (the flames) still work everywhere.

In this duplicated css, you need to change all references to the image flames.png to your new image, then do the same for the “delete vote” image (delete.png).

So:

background: url(flame.png) no-repeat 0 0;

…becomes…

background: url(cars.png) no-repeat 0 0;

Now change all height/width values in the duplicated css to match your new images. It's easy to work out - where the original css file has the width/height of a single sprite from the old image, then use the width/height from the new. If it has double the old image's height, then enter double the new image's height.

Step 4: Specify which CCK Fivestar field has our custom images

In order to make our new image sets display only on certain CCK fields, we need to make sure the scope of the css selector is correct. In my example case, I just add div.field-field-day-traffic before each of the static view selectors, because that's the class given to the div surrounding the field:

/*Override the traffic fields */
/* Static View-only Star Version - TRAFFIC (CARS)*/
div.field-field-day-traffic div.fivestar-widget-static .star {
  width: 20px;
  height: 20px;
  background: url(cars.png) no-repeat 0 0;
}

Note this will only work for the static view (i.e. when a fivestar field is displayed in the node's body), not for the node edit form. We get to that shortly.

Step 5: Give it a quick test

You should now be able to choose your new widget in your Fivestar settings, located at admin/settings/fivestar.

Test to make sure it works - make sure you try it out on a node with a Fivestar CCK field of the same name you specified in the css of Step 4. View the body of the node (static view of the Fivestar rating), and the node edit form (JavaScript view).

Of course, the Javascript view of the widget will still display the flames instead of the cars, and also presents a small problem. If you use Firebug to inspect the HTML of the widgets, you'll notice there's no way to discern which CCK field each one belongs to, and thus no way to select just that field for our new images. In my example, there's no way of telling just the Javascript traffic field to display the cars instead of flames - it's all or nothing. Each one is just a <div class="fivestar-form-item fivestar-labels-hover">.

Step 6: Override the theme function for Javascript Fivestar

The simplest solution I found was to override the theme function, enclosing the widget in a div with a more descriptive class. This goes in template.php:

<?php
/**
 * Customise the fivestar element, add another div so we know which icons to draw
 */
function phptemplate_fivestar($element) {
  // Add necessary css.
  fivestar_add_css();

  // Add necessary Javascript.
  if ($element['#widget'] == 'stars' ) {
    fivestar_add_js();
  }
  return '<div class="fivestar-' . $element['#parents'][0] . '">' . theme('form_element', $element, $element['#children']) . '</div>';
?>
}

The only difference from the original function is that instead of returning just theme('form_element', $element, $element['#children']), I enclose it in a div with the name of the field as a class. The name of the field is pulled from $element['#parents'][0]. The result of this for the traffic field will be:

<div class="fivestar-field_day_traffic">...widget...</div>

It's definitely not the most elegant solution, but unfortunately the Fivestar source code is a bit quirky. It doesn't really provide an opportunity to theme the widget's individual bits, as the work gets done in the function fivestar_expand(). I will provide a patch for the function, but until that gets committed, the easiest method is to use the above theme override.

Step 7: Add specific selectors into the css for the Javascript images

Back at the css file, you can now go to the section for the Javascript images, and using the new enclosing <div>, specify the field that will use the new images. My field was called field_day_traffic, so here's an example of the modifications:

div.fivestar-field_day_traffic div.fivestar-widget .cancel,
div.fivestar-field_day_traffic div.fivestar-widget .star {
  width: 20px;
  height: 20px;
}

And that should do it! Refresh the browser on the node edit page, and you'll see your new images used for the Javascript widget.

Here's screenshot of Hill Bomb's images. I use little road rollers for the road quality rating, as well as the cars and flames:

Final Ratings

There has been a suggestion that Fivestar be modified to allow this functionality to be set easily for each field, however it was marked as “won't fix” by the maintainers, for fairly sound reasons I think.

You could also argue that the custom CSS and images should be in the theme folder rather than the widget's folder, but I'll leave that issue alone. Just don't forget about the custom widget when you upgrade the Fivestar module.