Our newly released book, jQuery: Novice to
Ninja, contains a wealth of great ready-to-use
solutions, and teaches you both the basic and more advanced concepts of
jQuery as you progress. As a taster, here’s an excerpt wherein Craig Sharkie
shows us how to build a robust, sophisticated star rating widget using
jQuery. It’s keyboard-accessible, and allows for half-star ratings.
If you’ve never used jQuery before, grab the
free sample PDF and get up to speed with the introduction in Chapter
2. Chapter 7, from which this article is taken, is also included in the
download, so if you’d rather read this tutorial offline, you can do so at
your leisure.
You can get the sample code archive for this article here.
Our client wants to increase user engagement, and help his
users feel important. We’ve thought about this a bit, and tossed him a
star rating idea—after all, people love nothing more than to express their
feelings through the assignment of gold stars. Our shiny new control will
appear as shown in Figure 1, “Star rating control”.
The basis for our star rating control is a radio button group; it’s
perfect, as the browser enforces a single selection from the group. You
can select the range that you want the user to choose from, simply by
adding the correct number of buttons:
Example 1. chapter_07/11_star_ratings/index.html
(excerpt)
<div class="stars"> <label><input id="rating-1" name="rating" type="radio" value="1"/>1 Star</label> <label><input id="rating-2" name="rating" type="radio" value="2"/>2 Stars</label> <label><input id="rating-3" name="rating" type="radio" value="3"/>3 Stars</label> <label><input id="rating-4" name="rating" type="radio" value="4"/>4 Stars</label> ⋮ </div>
The hard part, of course, is replacing these radio buttons with our
star control. You can try to grapple with styling the HTML controls with
only CSS, but it will be much easier and more flexible if you split the
control into two parts: the underlying model that stores the data, and the
shiny view with stars. The model, in this case, is the original HTML radio
button group. Our plan of attack is to hide the radio buttons, and display
a list of links that we’ve added via jQuery, styled to look like stars.
Interacting with the links will switch the selected radio button. Users
without JavaScript will simply see the radio buttons themselves, which is
fine by us.
For the stars, we will rely on CSS sprites. This way our control
will only be reliant on a single image (shown in Figure 2, “Star CSS sprite image”), which saves on HTTP requests and makes it
easier for our graphic designers to edit.
Our CSS will apply the CSS sprite image to the links we create that
represent half-stars. To move between the different image states, we just
need to update the background-position
property:
Example 2. chapter_07/11_star_ratings/stars.css
(excerpt)
.stars div a {
background: transparent url(sprite_rate.png) 0 0 no-repeat;
display: inline-block;
height: 23px;
width: 12px;
text-indent: -999em;
overflow: hidden;
}
.stars a.rating-right {
background-position: 0 -23px;
padding-right: 6px;
}
.stars a.rating-over { background-position: 0 -46px; }
.stars a.rating-over.rating-right { background-position: 0 -69px; }
.stars a.rating { background-position: 0 -92px; }
.stars a.rating.rating-right { background-position: 0 -115px; }We’ve decided to make the user select a rating out of four stars,
rather than the usual five. Why? User psychology! Offer a person a middle
road and they’ll take it; by having no middle we make the users think a
bit more about their selection. We achieve better results, and we’ll be
better able to present users with the best content (as chosen by
them)!
But four is a limited scale—that’s why we want to allow for
half-star ratings. This is implemented via an optical illusion—you
probably noticed that our star images are chopped in half. Our HTML will
contain eight radio buttons, and they’ll each be worth half a star.
There’s two parts to the code for transforming our eight radio buttons
into four stars. First, the createStars function
will take a container with radio buttons and replace it with star links.
Each star will then be supplemented with the proper event handlers (in the
addHandlers method) to let the user interact with
the control. Here’s the skeleton of our starRating
object:
Example 3. chapter_07/11_star_ratings/script.js
(excerpt)
var starRating = {
create: function(selector) {
$(selector).each(function() {
// Hide radio buttons and add star links
});
},
addHandlers: function(item) {
$(item).click(function(e) {
// Handle star click
})
.hover(function() {
// Handle star hover over
},function() {
// Handle star hover out
});
}
}The create method iterates through each container matching the selector we
pass in, and creates a list of links that act as a proxy for the radio
buttons. These links are what we’ll style to look like stars. It will also
hide the original form elements, so the user only sees our lovely
stars:
Example 4. chapter_07/11_star_ratings/script.js
(excerpt)
$(selector).each(function() {
var $list = $('<div></div>');
// loop over every radio button in each container
$(this)
.find('input:radio')
.each(function(i) {
var rating = $(this).parent().text();
var $item = $('<a href="#"></a>')
.attr('title', rating)
.addClass(i % 2 == 1 ? 'rating-right' : '')
.text(rating);
starRating.addHandlers($item);
$list.append($item);
if ($(this).is(':checked')) {
$item.prevAll().andSelf().addClass('rating');
}
});We start by creating a container for the new links (a div element); we’ll be creating one new link for
each of the radio buttons we’re replacing. After selecting all the radio
buttons with the :radio selector filter, we take each
item’s rating and use it to create a hyperlink element.
For the addClass action, we’re
specifying the class name conditionally with a ternary operator, based
on a bit of math. As we’ve done earlier in the book, we’re using the
modulus (%) operator to determine whether the index
is even or odd. If the index is odd, we’ll add the rating-right class; this makes the link look like the
right side of a star.
Once our link is ready, we pass it to the
addHandlers method—this is where we’ll attach the
events it needs to work. Then, we append it to the list container. Once
it’s in the container, we see if the current radio button is selected (we
use the :checked form filter). If it’s checked, we want
to add the rating class to this half-star, and to all of the
half-stars before it. Any link with the rating class attached will be assigned the gold star
image (which will allow users to see the current rating).
To select all of the elements we need to turn gold, we’re going to
enlist the help of a couple of new jQuery actions:
prevAll and andSelf. The
prevAll action selects every
sibling before the current selection (unlike the
prev action, which only selects the immediately
previous sibling). For our example, we want to add the class to the previous siblings
and the current selection. To do so, we apply the
andSelf action, which simply includes the
original selection in the current selection. Now we have all of the links
that will be gold, so we can add the class.
You might have guessed that, along with
prevAll, jQuery provides us with a
nextAll method, which grabs all of an element’s siblings occurring
after it in the same container. jQuery 1.4 has also
introduced two new companion methods:
prevUntil and nextUntil. These are called with a selector, and will scan an
element’s siblings (forwards or backwards, depending on which one you’re
using) until they hit an element that matches the selector.
So, for example, $('h2:first').nextUntil('h2');
will give you all the elements sitting between the first h2 on the page and the following h2 in the same container element.
If you pass a second parameter to
prevUntil or
nextUntil, it will be used as a selector to
filter the returned elements. So, for example, if we had written
nextUntil('h2', 'div'), it would
only return div elements between our
current selection and the next h2.
After doing all this hard work, we can now add the new list of links
to the control, and get rid of the original buttons. Now the user will
only interact with the stars:
Example 5. chapter_07/11_star_ratings/script.js
(excerpt)
// Hide the original radio buttons
$(this).append($list).find('input:radio').hide();The control looks like it’s taking shape now—but it doesn’t do
anything yet. We need to attach some event handlers and add some behavior.
We’re interested in three events. When users hover over a star, we want to
update the CSS sprite to show the hover state; when they move away, we
want to revert the CSS sprite to its original state; and when they click,
we want to make the selection gold:
Example 6. chapter_07/11_star_ratings/script.js
(excerpt)
$(item).click(function(e) {
// React to star click
})
.hover(function() {
$(this).prevAll().andSelf().addClass('rating-over');
},function() {
$(this).siblings().andSelf().removeClass('rating-over');
});The hover function is the easiest: when hovering over, we select all of the
half-stars before—including the current half-star—and give them the
rating-over class using prevAll
and andSelf, just like we did in the setup. When
hovering out, we cover our bases and remove the rating-over class from all of the links.
That’s hovering taken care of.
Now for the click:
Example 7. chapter_07/11_star_ratings/script.js
(excerpt)
// Handle Star click
var $star = $(this);
var $allLinks = $(this).parent();
// Set the radio button value
$allLinks
.parent()
.find('input:radio[value=' + $star.text() + ']')
.attr('checked', true);
// Set the ratings
$allLinks.children().removeClass('rating');
$star.prevAll().andSelf().addClass('rating');
// prevent default link click
e.preventDefault();The important part of handling the click event is
to update the underlying radio button model. We do this by selecting the
correct radio button with the :radio filter and an
attribute selector, which searches for the radio button whose value
matches the current link’s text.
With the model updated, we can return to messing around with the CSS
sprites. First, we clear the rating
class from any links that currently
have it, then add it to all of the links on and before the one the user
selected. The last touch is to cancel the link’s default action, so
clicking the star doesn’t cause it to fire a location change.
I hope you’ve enjoyed this taste of all the jQuery goodness that’s
contained in jQuery: Novice to
Ninja. Remember to grab your free
sample PDF, which contains this example as well as 150 pages worth
of more great learning material. If you’re already sold, you can go ahead
and buy the
book straight from SitePoint.





