Star Rating using YUI (and Django)
04 Mar 2008I have a very good star rater on reviewsby.us, but it was written using some sloppy prototype code. I wanted to redo star raters in a well thought out manner and I wanted to use YUI. In this particular tutorial I will use Django although it is not a requirement.
For some background information on star raters, see this Yahoo! Design Pattern. Our pattern is more of a join star rater, similar to what’s found on Netflix: you see an average rating for a restaurant or dish unless you yourself have rated it.
This was a thought out design decision for our reviewsby.us redesign. Our site is primarily a personal utility that answers the question, “What dishes do I like at a particular restaurant?” If you haven’t rated something the website can only offer up an average and you can use that as a decision as to whether you should eat something or not.
If you have eaten something however, that average rating is irrelevant. You don’t need fellow meal advisors to tell you that you liked Chicken Makhani, you already know that for yourself. Therefore we show only your rating unless you haven’t rated something.
Working backwards
I like to “work backwards” as it were. Meaning, I like to just write the code that I ultimately will use to output a star rater. From there I will work on the supporting code that is necessary. I find by using this strategy, I can keep my code fairly clean and organized.
The template
So ultimately I want this: {\% star ‘mything’ 3 4 ‘/path/to/script’ %}
To show up as this:

Unfortunately Django templates doesn’t seem to have named attributes for template tags, so I’ll need to explain my syntax:
star
: is the template tag which we define below'mything'
: is an id string we will use for this rater and its associated objects3
: this is the second argument to star, it will be the users current rating, it can also be None4.1
: this is the third argument, it will be the average rating, it can also be None/path/to/script
: is the form that will process our rating
The HTML we want
Another developer had a good approach for handling star ratings and for handling Javascript in general. Create an underlying Javascript-free system, and then let the Javascript make it pretty. This is way to degrade gracefully.
Ultimately, I had my own approach to this problem, I wanted much of the visual lifting to happen on the CSS layer. So, we’ll use the following code:
A couple things to note in our HTML. Our unique string is restaurant
. It’s got an ID that is as unique as you want: rater_restaurant
where restaurant
was the first argument to our template tag. We use restaurant
to create some other unique IDs as well.
Also, this rating form makes a lot of sense semantically. While this form in its current state is a far cry from some ajaxy goodness, it makes clear sense as to what is going on.
The template tag
Well we know what we want from the HTML side, so let’s start coding our star
tag:
The CSS
A lot of work will happen via CSS. The CSS will remove quite a bit of the textual data that can be interpreted graphically with stars.
The strategy we use is to:
- fix the
UL
at a certain width with a background of grey stars - decorate the
LI.average
andLI.current
with repeating stars (blue and orange respectively) with az-index
of1
- decorate the
LI.average:hover
andLI.current:hover
with a transparent background - decorate
LI:hover input
as a colored in star and az-index
of2
This might not make sense now, until you see the CSS in full action. Also for the stars we’ll use a sprite of 3 stars. A grey defunct star as the default background, a blue star if it’s the average rating for an item and an orange star if it’s what the user wants.
I use the following sprite:

The following CSS will do some magic:
The inline-block
value for display
is not supported very well. I recently switched to Firefox 3 Beta and it renders as expected. Firefox 2 has problems with it. I may revise the CSS later to accommodate it.
The Javascript
The fundamental drawback to the design here, is that it really only works well with the Javascript on. In fact, with the CSS on and Javascript off, this code will not work very well for the end user. This too will be revised in the future.
Our Javascript needs to do something very simple:
- extract the star value you clicked on
- send it to the server
- redraw the stars
It’s a very simple operation, but I honestly think other libraries have an advantage to YUI in this regard.1 Here’s some unobtrusive code I came up with:
Note: I intentionally left out irrelevant pieces of code, like the function definition of MA.is_authenticated()
, this code isn’t meant for cutting and pasting, it’s meant for cutting-pasting and then some careful editing.
The callback view
The callback script is what you specify when you call {\% star ... %}
. The view I use is as follows:
That code is oversimplified… you have to write your own logic as it applies to our site. The rating.html
is simply the call to your star tag:
{\% load tags %}
{\% star 'mything' restaurant.current_rating restaurant.average_rating restaurant.get_rating_url 1 %}
Note the 1
at the end. It’s a flag to turn off the outer span
so we can just insert the guts back into the original span
.
Final Thoughts
The star-rater is really a large problem that’s hard to tackle in one sitting and quite frankly is not documented well anywhere. The code I’ve provided is a shadow of the real code I’ll be using, but hopefully it’s enough to get you started.
I definitely will update my production code to solve a few outstanding issues, as I mentioned above. I’ll try to update this tutorial at the same time. If there are questions about the examples given, feel free to ask and I’ll attempt to answer.
- YUI has the
Selector
and that might alleviate some problems, but jQuery has very nice selection capabilities. In this future version I keep promising, I'll use the YUISelector
↩