Simple Star Rating with CSS, JQuery & Ajax
9/25/2009
Try It, Click a Star
How it Works
I'd almost finished integrating the Fyneworks JQuery Star Rating Plugin into an MVC project when I started to get a bad feeling about the size of it, though it worked fine and seemed to perform quickly. After adding plugin after plugin I just started to balk at more code and I didn't need all the functionality of the fyneworks plugin anyway. So I set out to code my own lighter weight solution. My UI requires that a user clicks a star to indicate a rating, instead of first selecting a rating and then clicking a separate submit button. In the end this might not be a good design. Maybe users will click too soon or not understand that the stars are clickable. Still, I could add a visual clue or two to mitigate these concerns. I could send back a ratingID after the first post and allow the user to edit the rating. (BTW, billb.name doesn't capture rating info. The server side code running for this demo simply spits your rating back at you.)These Classic ASP files are going to look odd to you but I find myself developing browser side code using a text editor and Classic ASP pages; interpreted languages and stripped down environments are pleasingly snappy. (I used to use TCL to prototype C code.) When the code is working I copy it into my ASP.NET MVC project.
So, this demo consists of two .asp files:
- Rating.asp - contains the html, css and javascript.
- RatingProcess.asp - processes the form post from Rating.asp
The trick is one image that contains both the selected and un-selected stars; one star is 20px square so the image is 20px wide and 40px high. (I used a pitifully old Paint Shop Pro 9 to make it.) You put the image in the background of your element and set the width and height to match the size of one image; in my case that's 20 x 20. Then set the background-position to correspond to the image you want to see. It's like I've made a little window and beneath it I'm moving a sheet of images around so one image appears through the window at a time. So, I my eaxmple, on a hover I set the image position down 20px so the lower image shows through the window.
But my code shows that I drop it down 21px!!?? Well, being a lame graphics person, my stars.png file was 1 pixel off; the lower star should have been 1px lower. Instead of fixing it, I just used code. If the image is off a hair, you can play with the CSS positioning in the code to get them lined up perfectly.
If you're using a CSS-only, no-javascript design, CSS-Sprites is a way to deal with the fact that you can't pre-load images without Javascript. Web designers will often put many images into one file; I looked at Yahoo's home page and found an image that actually contained about 20 images within. (There are other techniques to load images with just css, like hiding them behind other images or off the screen.)
Since I'm using javascript anyway, to loop through the 5 stars, I used mouse events instead of CSS hover but I still used the Sprites idea of multiple images in one file just for fun. I could have used separate images and done a pre-load and replaced the image in the mouse events instead of the css background-position trick.
All the code appears below. You can copy and paste it and experiment with it a bit. Maybe instead of buttons you'd use div's or span's. Maybe change the cursor to a hand during a mouseover. Maybe find another solution for dealing with multiple submit buttons on one form. You'd also probably add a little more code to disable the stars somehow once a rating has been posted, hide them maybe, unless you wanted to allow the user to edit their previously posted rating. Anyway, here's the code; it's light and it works. Hope the comments will clarify things.
Client side code - Rating.asp
<html>
<head>
<style>
.star
{
display: inline; /* Keeps the stars in one line*/
border: 0; /* Removes the buttons border*/
width: 20px;
height: 20px; /* One-half the height of the stars.png image*/
background: url("images/stars.png") no-repeat 0px 0px; /*Set image to the top unselected star*/
}
</style>
<script type="text/javascript" src="scripts/jquery.js"></script>
<script type="text/javascript">
$(document).ready(function(){
// The post is ajax style because it returns false
$('#ratingForm').submit(function() {
$.post($(this).attr('action'),
$(this).serialize(),
function(response) {
// Display the rating that the user clicked
$("#ratingDisplay").html("Your rating was " + response + " stars.");
},
"text"
);
return false;
});
// I added this click event to update the hidden field in the form that saves
// the id of the clicked button. (id's are simply numbers, 1 - 5).
// Maybe there's a better way to find which button was clicked.
$('.star').click(function() {
$('#rating').val(this.id);
});
$('.star').mouseover(function() {
var id = $(this).attr('id');
for (i=1;i<=id;i++)
{
// This shows how to use a variable inside a JQuery selector.
// Change the background-postion to show the bottom star
$('#' + i).css('background-position', '0px -21px');
}
});
$('.star').mouseout(function() {
var id = $(this).attr('id');
for (i=1;i<=id;i++)
{
// Change the background-postion back to show the top star
$('#' + i).css('background-position', '0px 0px');
}
$(this).blur(); //remove dotted outline
});
}); /*End Ready*/
</script>
</head>
<body>
<h2>Try It, Click a Star</h2>
<form id="ratingForm" action='RatingProcess.asp' method="POST">
<input type="hidden" id="rating" name="rating" value='0' />
<!--Notice that all the button tags are run together - That way
there's no space between the stars and the mouseovers look better. -->
<button type='submit' class="star" id="1" ></button><button type='submit' class="star" id="2" ></button><button type='submit' class="star" id="3" ></button><button type='submit' class="star" id="4" ></button><button type='submit' class="star" id="5" ></button>
</form>
<div id="ratingDisplay"></div>
</body>
</html>
Server side code - RatingProcess.asp
RatingProcess.asp is just for testing; make sure my post is working right. All it does is grab the value from the request and spit it back into the response.
<%@ LANGUAGE="VBSCRIPT" %>
<% response.write(request.form("rating")) %>