Creating an AJAX Rating Widget

print icon
  1. Introduction
  2. Getting started with the HTML markup
  3. Adding dynamic behaviour
  4. Connect the widget to the server with different frameworks
  5. Conclusion

Introduction

Frequent visitors of ajax enabled websites, like ajaxian, have all witnessed them already: ajax rating widgets. They are flashy, animated, you can use them to rate the content (usually without refreshing the page) and if you could, you'd present them to your parents and marry them. Compared to the classic rating system, as on IMDb, they incite people to click them, reducing the effective rating process to only one click.
In this tutorial, I want to show you how to create the JavaScript framework to display the animated rating widget and how to connect it to your server backend by using some of the most common Ajax frameworks out there. I clearly separate the page creation from the JavaScript functions and the rating backend, to allow the script to be as flexible as possible and to be easy integrable into your existing website.
This tutorial is not meant to present you with a finished script (even though you could simply copy&paste the end result into your website and make it work without any problems), but rather to explain the design and implementation process that would enable you to create your own widgets if you'd need to.

Getting started with the HTML markup

The first thing we're going to do is to create the div containers where we'll put our rating widget inside. This simple task already requires careful planning, as it will greatly influence the rest of our script. The problem is that the div containers need to be clearly identifiable by the JavaScript, to transform the static content into our dynamic rating widget and yet, they need to be distinguishable if you'd happen to have several rating widgets on the same page. If you read the quirksmode W3C DOM tables, you'll notice that only have two functions we can use to access the elements within our webpage, getElementById() and getElementsByTagName(). I decided to use the second function, getElementsByTagName() to read all the div containers in my webpage, and then check on the className to identify my rating widgets. This is why we'll give our rating widgets all the same class, namely "rating". To distinguish our widgets, we'll also give them a unique id, which is made of a common prefix "rating_" and a number that could serve as ID in the database backend. The same class name also provides us a nice markup hook to apply our CSS to.
Finally, we need to store the value of the current ratings somewhere, without complicating the server-side code generation too much. I decided to simply store the value of the rating inside the container. The finished div container for a rating widget will then look like the following:
<div class="rating" id="rating_1">3.5</div>
It is most important that there are no line breaks in this code, as this will complicate the DOM tree unnecessarily. If you are uncertain about what I just said, please read the W3Schools HTML DOM Tutorial for further reference, since we are going to access the DOM directly from within our JavaScript.
As you can see, the div container is pretty easy to generate in the server-side scripting language, requiring most of the time only one line:
printf("<div class="rating" id="rating_%s">%s</div>", ratingId, rating);
Following the guideline that we strictly want to separate content from design and behaviour, this is all we have to do in our HTML code. The next chapter describes how to transform each div container into a fully functional rating widget.

Adding dynamic behaviour

At the moment, the result is not very exciting and definitely not what we want to achieve. The next step is to add our JavaScript code to transform the plain div container into our dynamic rating widget. To do so, we first have to add our JavaScript file to the HTML header and then hook our initialization function to a window event. This is not a simple task and there are many different ways to achieve the same result. In his blog entry, Peter Michaux explains the window.onload problem and presents several techniques to overcome some of the problems. Advanced Ajax frameworks usually include special events that have the same result, such as dojo's dojo.addOnLoad() or mootools window.onDomReady().
In the first version of the code (the one that doesn't use any specific Ajax framework), I decide to simply hook my JavaScript function to the window.onload event:
window.onload = init_rating;
This line will simply call the function init_rating() when the page is fully loaded, parsed and rendered, which is also one of the problems of this method, in case your page happens to be very image heavy.
Our next step is to write the init_rating() function, which will do the following:
  1. Find all the containers that represent a rating
  2. Read the rating value contained within the div container
  3. Remove the rating value text
  4. Create and add the images to the rating widget
  5. Add the animation effects

Find all the rating containers

Remember when I said that it was important to think about how to design the div container markup so we could use it in our JavaScript code? This is the place where our design will influence the complexity of the code and fortunately, we can simplify the code a lot. First, we use getElementsByTagName() to read all the div containers in our page into an array, then we loop over the array and use getAttribute to read the class name of the div container and finally, we filter all the elements out that don't have the class name we gave to our rating containers:
var ratings = document.getElementsByTagName('div');
for (var i = 0; i < ratings.length; i++)
{
    if (ratings[i].className != 'rating')
        continue;
        
    // rest of our code
}
The "continue" keyword in our JavaScript code, just like in most other programming languages, continues with the next iteration of the loop and prevents the execution of the rest of the code during that iteration.

Read the rating value

Now that we have all the rating containers, we can start by reading the rating value written as text inside the div container. To read that value, we access the first child node of the div container, which is a TextNode, and access its nodeValue, which returns the text CDATA in case of text nodes. This is done by the following line:
var rating = ratings[i].firstChild.nodeValue;
You probably remember when I said earlier that it is important not to insert unnecessary line breaks in the HTML markup of the div container. Line breaks are also stored in TextNodes, modifying the text content from the desired "4" to a more complex "n     4" (taking 4 as example rating). It is still possible to convert that into a number but why introduce unnecessary complications if we simply think ahead and take care of the line breaks during HTML generation.

Remove the rating value

Even though we just read the rating value, we still have the TextNode inside our div container. Of course, we need to remove that node first before we can start adding the images to the container. This is done using the removeChild() DOM function:
ratings[i].removeChild(ratings[i].firstChild);

Add the images to the widget

Finally, we have to create and add the images to our rating widget. I decided to use 4 different stars images (Rating Star OnRating Star OffRating Star HalfRating Star Over), which represent the "on", "off", "half" and "over" states. The "on" state represents a fully coloured star and is used if the rating exceeds that star value. The "off" state represents a grey star and is used if the rating is inferior to that star's value. The "half" state adds the possibility to have decimal ratings but limiting the value of ".5" only. The "over" state is used when the mouse moves over the star, adding the flashy animation effect.
The first thing we need to do is to add a simple error check mechanism: we need to test if the rating value we read is within the allowed limits of our widget. To do so, we define a constant value called "NUMBER_OF_STARS", which defines the upper bound of our widget. We then test if the rating is contained within the limits:
var NUMBER_OF_STARS = 10;

if (rating > NUMBER_OF_STARS || rating < 0)
            continue;
There is no graceful way to recover from that error, so I simply decided to continue with the next rating container and prevent that error from happening within the server backend.
Now, we need to loop over the number of stars that are displayed and check what the image graphic will be that is displayed on the star. Using the HTML DOM function createElement(), we initialize a new image and progressively add the respective values to that element. Of course, the first thing we are interested in is the displayed image: we could either test the rating against the current iteration value or we could decrement the rating value during each iteration and test against 1, 0.5 and 0. I decided to decrement the rating during each iteration, which presents me with three simple cases to test: if the rating is greater than or equal than 1, the star is "on", if the rating is exactly 0.5, the star is "half" otherwise, the star is "off". This results in the following code:
for (var j = 0; j < NUMBER_OF_STARS; j++)
{
    var star = document.createElement('img');
    if (rating >= 1)
    {
        star.setAttribute('src', './images/stars/rating_on.gif');
        rating--;
    }
    else if(rating == 0.5)
    {
        star.setAttribute('src', './images/stars/rating_half.gif');
        rating = 0;
    }
    else
    {
        star.setAttribute('src', './images/stars/rating_off.gif');
    }
    ratings[i].appendChild(star);
}
The last line appends the newly created image to the div container. If you run this code, you should already see the stars with the correct states.
Now, before adding the graphical effects, we need to reason a little about what is required to animate the correct rating widget. A rating widget is distinguished by its ID, which is a number and represented in the widget id, as explained in the HTML markup chapter. Every method that is applied onto our stars, need to have several information, like the widget id and the star id, which requires us to extract the widget id and save it somewhere. Since the widget is is stored inside the div container id, it is possible to extract that number from the id:
var widgetId = ratings[i].getAttribute('id').substr(7);
I use the substr() method to cut off the first 7 characters representing "rating_" and leaving only the number.
Now that we have the widgetId, we can add the animation effects, using the onMouseOver and onMouseOut events:
var widgetId = ratings[i].getAttribute('id').substr(7);
star.setAttribute('id', 'star_'+widgetId+'_'+j);
star.onmouseover = new Function("evt", "displayHover("+widgetId+", "+j+");");
star.onmouseout = new Function("evt", "displayNormal("+widgetId+", "+j+");");
As you can see, I don't hoop onto the onClick event just yet, since I plan to do so using the specific functions of the Ajax frameworks. In order to prepare for those functions, I save the widgetId and the star number already in the image id, which I can use later to extract those values again.
Finally, there is one last thing to consider, which is important for the animation effect to work later on. The displayHover() function will replace the image sources with the "over" state graphic and we will lose the current state information of the star. To remember its state before the effect happens, we save its state in the class name, allowing us to read the class name and displaying the correct image again, as soon as the mouse leaves the widget. This results in this final function:
var NUMBER_OF_STARS = 10;

function init_rating()
{
    var ratings = document.getElementsByTagName('div');
    for (var i = 0; i < ratings.length; i++)
    {
        if (ratings[i].className != 'rating')
            continue;
            
        var rating = ratings[i].firstChild.nodeValue;
        ratings[i].removeChild(ratings[i].firstChild);
        if (rating > NUMBER_OF_STARS || rating < 0)
            continue;
        for (var j = 0; j < NUMBER_OF_STARS; j++)
        {
            var star = document.createElement('img');
            if (rating >= 1)
            {
                star.setAttribute('src', './images/stars/rating_on.gif');
                star.className = 'on';
                rating--;
            }
            else if(rating == 0.5)
            {
                star.setAttribute('src', './images/stars/rating_half.gif');
                star.className = 'half';
                rating = 0;
            }
            else
            {
                star.setAttribute('src', './images/stars/rating_off.gif');
                star.className = 'off';
            }
            var widgetId = ratings[i].getAttribute('id').substr(7);
            star.setAttribute('id', 'star_'+widgetId+'_'+j);
            star.onmouseover = new Function("evt", "displayHover("+widgetId+", "+j+");");
            star.onmouseout = new Function("evt", "displayNormal("+widgetId+", "+j+");");
            ratings[i].appendChild(star);
        } 
    }
}

Add the animation effects

To finish the display of our rating widgets, we only need to complete the two function displayHover() and displayNormal(). This shouldn't be too much of a problem. Both functions receive as argument the widgetId and the star number, making it very easy to access the necessary elements in our DOM tree.
The displayHover() function has to iterate over all the stars within the correct rating widget up to the current star the mouse is over and change their graphics to the "over" state:
function displayHover(ratingId, star)
{
    for (var i = 0; i <= star; i++)
    {
        var starElement = document.getElementById('star_'+ratingId+'_'+i)
        starElement.setAttribute('src', './images/stars/rating_over.gif');
    }
}
As you can see, I don't have to use the troublesome childNodes in the DOM navigation but I can immediately use the star id that already contains the widgetId and the star number.
The displayNormal() function is similar to the displayHover() function with the only difference that the newly displayed image is dependent on the saved state inside the class name:
function displayNormal(ratingId, star)
{
    for (var i = 0; i <= star; i++)
    {
        var status = document.getElementById('star_'+ratingId+'_'+i).className;
        var starElement = document.getElementById('star_'+ratingId+'_'+i);
        starElement.setAttribute('src', './images/stars/rating_'+status+'.gif');
    }
}

Conclusion

Our current JavaScript allows us to transform specially marked div containers into animated rating widgets that we can use in specialized Ajax frameworks to link to our server backend. The HTML markup is completely separated from the JavaScript code, which will leave the user with a not very stylish but still visual display of the current rating in case JavaScript is disabled. I've put together a ZIP file with the current result, containing an HTML file, a CSS file, the JavaScript file and the images.

Connect the widget to the server with different frameworks

Please make sure to delete the "window.onload=init_rating;" line in the "script.js" file if you downloaded the .zip file, since we're using the specialized framework onload event.
The examples below are only an illustration of how this given task is achievable with a variety of different JavaScript frameworks. It should not be used as a reference about what framework is superior since not every framework follows the same route and has the same objectives. Before jumping to conclusion, you should read more sources and also hear every site. There are good reasons for dojo not having the $ operator and some interesting points can be found here (in the comments). When you need to decide which framework to chose, base your decision on your specific task and the framework you're most comfortable with.

The PHP backend

Since we want to focus fully on the clientside frontend, we'll just use the most simple PHP script that reads the values it receives through the GET method and outputs a message in plaintext back to the client. Nothing fancy, nothing useful and totally outside of the scope of this tutorial:
<?php
  header('Content-type: text/plain');
  print "Thank you for rating the content ".$_GET['ratingID']." with the value ".$_GET['value'];
?>
You'll probably notice that the lowest value a user can rate is 0, since our stars are numerated from 0 to NUMBER_OF_STARS-1. It is up to you to change your PHP scripts to take that into account and increment the received value adequately.

Using the Dojo framework

The first framework I want to demonstrate is the Dojo 0.4.2 javascript toolkit. Dojo uses a powerful library system, making it possible to only load (and download) the necessary packages for your application to work. Before you can use dojo, you have to download and install dojo, which basically comes down to unzipping the archive (in .tar.gzip format) and placing it in the correct folder, in my case "./js/dojo". To use dojo in your JavaScript, you have to link to the dojo.js file that contains the framework to load additional packages. A more detailed tutorial can be found in the Dojo Book.
Before we're starting with the scripting, make sure that your header looks like the following, where script.js is the code described in chapter 3:
<script src="js/script.js" type="text/javascript"></script>
<script src="js/dojo/dojo.js" type="text/javascript"></script>
Dojo uses an Event System that makes it possible to listen to certain user actions and react accordingly. The event system is contained within "dojo.event.*" that we have to load before using it. Since Dojo overwrites the window load and unload events, it also provides a special event called dojo.addOnLoad() that we can use to add function calls to the window load event. This means we have to remove the window.onLoad event that we previously used and use the new method to call our function. Additionally, we need to add an event listener to every rating widget within our website.
To add an event listener, dojo provides a method called dojo.event.connect() that takes the object to listen to, the event and the callback function (the function that is executed when the event happens). Since we need such an event listener to every star in every rating widget, we have to use the same approach as earlier to read out all the div containers representing rating widgets and then iterate over all the childs and add the event listener:
function dojoInit()
{
    init_rating();
    var ratings = document.getElementsByTagName('div');
    for (var i = 0; i < ratings.length; i++)
    {
        if (ratings[i].getAttribute('class') != 'rating')
            continue;
        
        for (var j = 0; j < ratings[i].childNodes.length; j++)
        {
            dojo.event.connect(ratings[i].childNodes[j], 'onclick', 'submitRating');
        }
    }
}
dojo.addOnLoad(dojoInit);
As you can see, I used the dojo.addOnLoad() method to call the dojoInit() function when the site has fully loaded. The first thing I do in dojoInit() is to call our init_rating() function that does everything described in chapter 3. Afterwards, I read all the div containers in the page, filter them using their class name and finally iterate over all the children, using the DOM property childNodes[] and add the dojo event listener to them, having them call submitRating() when someone clicks on them.
As you can see, we have no possibility to specify the arguments to the submitRating() method. By default, dojo provides one argument, which is an Event Object, that we can use to identify the object that was clicked. The "target" property of the Event Object is a pointer to the clicked object within the DOM tree and we'll use it to read its id, where we earlier stored the widgetId and the star number. The id has the format "star_widgetId_number" and we can use several consecutive substr() calls to read the values:
function submitRating(evt)
{
    var tmp = evt.target.getAttribute('id').substr(5);
    var widgetId = tmp.substr(0, tmp.indexOf('_'));
    var starNbr = tmp.substr(tmp.indexOf('_')+1);
}
Now that we have the two values, all that is left to do is to create an asynchronous call to the submitRating.php script using the dojo io package. This package provides a powerful tool to send and receive asynchronous server calls using the XMLHttpRequest object. I won't enter to much into the details of how the I/O package works but we'll use the dojo io bind method that takes several arguments, among which the url, the callback handler and then content are the most important. The url is the location of our server-side script, the callback handler is the function that is called when a new event occurs (more specifically when the onreadystate event of the XHR object occurs) and the content are the GET arguments sent to the server. The code should look like this:
function submitRating(evt)
{
    var tmp = evt.target.getAttribute('id').substr(5);
    var widgetId = tmp.substr(0, tmp.indexOf('_'));
    var starNbr = tmp.substr(tmp.indexOf('_')+1);
    dojo.io.bind({
               url: './php/submitRating.php',
               handler: function(type, data, evt)
                  {
                    if (type == 'error')
                      alert('Error submitting the rating!');
                    else
                      alert(data);
                  },
               content: {'ratingID': widgetId, 'value':starNbr}
            });
}
As you can see, I used an anonymous function as callback handler, that takes the dojo predefined three arguments type, data and evt of which type and data are the most important. The "type" argument is used to check if something went wrong during the request (you could also register an error handler using the "error" argument of the dojo.io.bind() method) and "data" contains the received content from the server-side script, in this case the plaintext message.
The entire code, using the dojo 0.4.2 framework, should look like the following:
<script type="text/javascript">
dojo.require("dojo.event.*");
dojo.require("dojo.io.*");

function submitRating(evt)
{
    var tmp = evt.target.getAttribute('id').substr(5);
    var widgetId = tmp.substr(0, tmp.indexOf('_'));
    var starNbr = tmp.substr(tmp.indexOf('_')+1);
    dojo.io.bind({
               url: './php/submitRating.php',
               handler: function(type, data, evt)
                  {
                    if (type == 'error')
                      alert('Error submitting the rating!');
                    else
                      alert(data);
                  },
               content: {'ratingID': widgetId, 'value':starNbr}
            });
}

function dojoInit()
{
    init_rating();
    var ratings = document.getElementsByTagName('div');
    for (var i = 0; i < ratings.length; i++)
    {
        if (ratings[i].getAttribute('class') != 'rating')
            continue;
        
        for (var j = 0; j < ratings[i].childNodes.length; j++)
        {
            dojo.event.connect(ratings[i].childNodes[j], 'onclick', 'submitRating');
        }
    }
}
dojo.addOnLoad(dojoInit);
</script>

Using the mootools framework

mootools is a compact, modular, Object-Oriented JavaScript framework that I used initially because of its powerful Accordion widget. The small size, the customized download and the nice effects makes it a good choice for intermediate programmers that want something small that "just works".
Before we can even think about using mootools though, we need to assemble all the important parts in the download section of the page. This can be a little confusing for beginners, since a missing package keeps your script from running correctly (if at all). It is of course possible to just select everything (for which there exists a shortcut, simply enter "javascript:Download.all();" in your browser's location bar and press Enter) but that increases unnecessarily the code size. Remember that every visitor to your site has to download the JavaScript file so try to keep it as small as possible.
The required parts for our script to work are: "Element", "Event", "Dom", "Window.Base" and "Ajax". The "Element" package allows us to add function callbacks to element events and we'll use it to hook onto the onClick event of our stars. The "Event" package is provides cross-browser methods to manage events and although we only use the target property of our events, I consider this to be a good idea. The "Dom" package allows us to select the div containers using their css class. The "Window.Base" package contains window events, such as the onDomReady event that we'll use to call our initialization function instead of window.onload(). Finally, the "Ajax" package extends the XMLHttpRequest wrapper package XHR with additional methods and functionalities and provides a cross-browser method to send Ajax requests.
Before we'll start with the JavaScript code, we have to add the mootools library to our website. This is simply done by writing:
<script src="js/mootools/mootools.js" type="text/javascript"></script>
The structure of the program will strongly resemble to the dojo code. The things that do change are the initialization call, the access to the rating widgets, the event listener and the Ajax request. As already mentioned, the "Window.Base" package provides us with a method called window.onDomReady() that takes as argument the function to call when the DOM is ready, which means when the document tree has loaded, not waiting for images (which is a good thing). The use of that method is shown here:
window.onDomReady(mooInit);
The mooInit() function does the same thing as the dojoInit() function; it finds all the stars within our page and adds an event listener to the onClick event. To access the rating widgets, we can use the $$ operator, that allows us to access elements by their CSS class name, to get all the rating widgets and the getChildren() method to get an array of all the stars images. To iterate over the array, mootools provides a method called each that is located in the Array package. each expects as argument a function and passes a reference to the current element as argument to the function. The "Element" package provides us with the method addEvent() which attaches an event listener to a DOM element:
function mooInit()
{
    init_rating();
	$$(".rating").getChildren().each(function(star) {
	    star.addEvent('click', submitRating);
    });
}
As you can see, I defined an anonymous function that has one argument called star. This function is called once for every item in the array returned by getChildren(). Please notice that the addEvent() is a method of the DOM element, contrary to dojo where the DOM element is an argument. Now, all that is left to do is to create the callback function submitRating(evt) and to send the Ajax request. mootools provides a specialized package called "Event" that provides a cross-browser compatible way of using events. The first thing we need to do within our function is to initialize an Event object. This is done by passing the standard event argument as argument to the Event object. Then we can use the Event object's target property and don't worry about the underlying implementation.
In order to send an Ajax request, mootools provides a special package called Ajax.js. The object constructor takes two arguments, the url and the options. Among all the possible options, only two are useful for our purpose, the method and the onComplete event handler. For our rating widget, we'll specify that the method will be "get" and the event handler will be once again an anonymous function that simply alerts the returned message. To send the request, the Ajax object has the request() method. Contrary to the dojo framework, that provided an option where we could specify the get arguments and values, mootools provides no special tool to specify those values. We are required to construct the respective url ourselves. To facilitate this task, mootools provides a method called Object.toQueryString() that takes key/pair values and transform it into the corresponding url. The code to send the Ajax request should look like the following:
function submitRating(evt)
{
    var tmp = new Event(evt).target.getAttribute('id').substr(5);
    var widgetId = tmp.substr(0, tmp.indexOf('_'));
    var starNbr = tmp.substr(tmp.indexOf('_')+1);
    var queryString = Object.toQueryString({'ratingID': widgetId, 'value': starNbr});
    var myAjax = new Ajax('./php/submitRating?'+queryString, {
        method: 'get',
        onComplete: function(result)
            {
                alert(result);
            }
        }).request();
}
The entire code, using the mootools 1.00 release version, should look like the following:
<script src="js/mootools/mootools.js" type="text/javascript"></script>
<script type="text/javascript">


function submitRating(evt)
{
    var tmp = new Event(evt).target.getAttribute('id').substr(5);
    var widgetId = tmp.substr(0, tmp.indexOf('_'));
    var starNbr = tmp.substr(tmp.indexOf('_')+1);
    var queryString = Object.toQueryString({'ratingID': widgetId, 'value': starNbr});
    var myAjax = new Ajax('./php/submitRating?'+queryString, {
        method: 'get',
        onComplete: function(result)
            {
                alert(result);
            }
        }).request();
}


function mooInit()
{
    init_rating();
	$$(".rating").getChildren().each(function(star) {
	    star.addEvent('click', submitRating);
    });
}

window.onDomReady(mooInit);
</script>

Using the jQuery framework

The jQuery library is designed to change the way that you write JavaScript and oh boy, they don't exaggerate. jQuery might not have the fanciest widgets or the the most beautiful effects ever but you can do some really cool stuff with it. But first things first, before we can do anything with it, we need to add the JavaScript file to our website. This is done by first downloading the latest jQuery release and then add it to the code, using:
<script src="js/jquery/jquery.js" type="text/javascript"></script>
Once again, we have to deal with the same problems as usual, which is add the initializer to the windows.onload event, add an event listener to the click event of every star in every rating widget and finally send the Ajax request.
To add a function call to the window.onload event, jQuery offers its own method which is called $(document).ready(). Basically, this uses already one of the most powerful features of jQuery, which is the Selector. It is a combination of CSS 1-3, XPath and some custom modification and allows you virtually to access every element within your page. In this simple example, we use the selector to access the document element and attach a function to the ready event:
$(document).ready(jqueryInit);
The next problem would be to add an event listener to each star image in every rating widget. While mootools offered us the possibility to use the $$ operator to access all the div containers that had the "rating" class, jQuery offers us a little more. Not only can we use the Selector to access the div containers by their class name, we can also use the XPath expression to access their children of type "img". If that's not enough, the bind() method, that is used to add an event listener to an element event, can be used on collection of objections (instead of iterating over all the objects of a collection and using the method on each object individually). Not convinced? Here's the code:
function jqueryInit()
{
    init_rating();
    $('div.rating/img').bind('click', submitRating);
}
There's another thing worth mentioning: the exact same thing could maybe be possible with the other frameworks I presented but honestly, I couldn't find it in their documentation. jQuery not only offers a good JavaScript framework, but also a great documentation that made it very easy to find all those things.
Finally, all that is left is to send the Ajax request. This is done by using the $.get() function, which is basically an higher-level abstraction of their low-level Ajax implementation. The function has three argument, the url, the parameters and the callback function:
function submitRating(evt)
{
    var tmp = evt.target.getAttribute('id').substr(5);
    var widgetId = tmp.substr(0, tmp.indexOf('_'));
    var starNbr = tmp.substr(tmp.indexOf('_')+1);
    $.get("./php/submitRating.php",
       { ratingID: widgetId, value: starNbr},
       function(data){
         alert(data);
       }
     );
}
The entire code, using the jQuery 1.1.2 library, should look like the following:
<script src="js/jquery/jquery.js" type="text/javascript"></script>
<script type="text/javascript">

function submitRating(evt)
{
    var tmp = evt.target.getAttribute('id').substr(5);
    var widgetId = tmp.substr(0, tmp.indexOf('_'));
    var starNbr = tmp.substr(tmp.indexOf('_')+1);
    $.get("./php/submitRating.php",
       { ratingID: widgetId, value: starNbr},
       function(data){
         alert(data);
       }
     );
}

function jqueryInit()
{
    init_rating();
    $('div.rating/img').bind('click', submitRating);
}

$(document).ready(jqueryInit);
</script>

Using the prototype framework

Next on our list is the prototype framework, that aims to ease development of dynamic web applications. Even though I've never used prototype before, the prototype API has a great documentation since mid-January and if you know what you're looking for, you should have no problems finding it (apart from the window.onload event that was hidden in an example somewhere deep within).
I am using the 1.5.0 version and, as usual, the very first thing to do is to add the script to your header:
<script src="js/prototype/prototype.js" type="text/javascript"></script>
If you read through the other framework examples, you should know by now that we have three simple tasks to complete: hook an initialization function to the window.onload event, add a "click" event listener to each star in every rating widget and send the Ajax request. The Event manager in prototype uses a method called observe() that registers a callback function to an element event. To hook onto the onload event, we need to observe the "window" element and register the "load" event:
Event.observe(window, 'load', prototypeInit);
In order to register an event handler for every star in each rating widget, prototype provides several utility methods, such as the $() method to return an element based on its id or the $$() method that returns elements based on their CSS class. In their Enumerable package, prototype has several methods that work on collections of values. One of these methods is the each() method that basically iterates over every element of a collection and returns the element, similar to foreach() operations in programming languages such as PHP. Using the each() method, our prototypeInit() function looks like the following:
function prototypeInit()
{
    init_rating();
    $$('.rating').each(function(n){
                        n.immediateDescendants().each(function(c){
                            Event.observe(c, 'click', submitRating);
                        });
                    });
}
As you can see, I use the $$() method to get an Enumeration of div containers (the rating widgets), then use each() to iterate over each div container. The parameter "n" is the Enumerable, a reference to the current object in the iteration. It is not possible to apply each to the standard "childNodes" property of a DOM element, so you have to use the method immediateDescendants() to get a collection of star images. Finally, we apply each() on that collection to iterate over each star image and use observe to register an event handler to it.
For our submitRating event, prototype provides a cross-browser way of accessing event information, that are explained in the Event package. We only have to initialize a new Event object and pass our event as argument.
Finally, to send the Ajax request, we'll use the Ajax.Request() method that takes two arguments, the url and the options. The only Ajax Options that interest us are the "method", the "parameters" as well as the "onSuccess" handler. Please note that there are several existing callback handlers, such as onComplete, onFailure, onException and many more, but for the sake of simplicity, we'll only use the onSuccess handler and discard the rest:
function submitRating(evt)
{
    var tmp = Event.element(evt).getAttribute('id').substr(5);
    var widgetId = tmp.substr(0, tmp.indexOf('_'));
    var starNbr = tmp.substr(tmp.indexOf('_')+1);
    new Ajax.Request('./php/submitRating', {
      method: 'get',
      parameters: {ratingID: widgetId, value: starNbr},
      onSuccess: function(transport) {
          alert(transport.responseText);
      }
    });
}
The entire code, using the prototype 1.5.0 framework, should look like the following:
<script src="js/prototype/prototype.js" type="text/javascript"></script>
<script type="text/javascript">

function submitRating(evt)
{
    var tmp = Event.element(evt).getAttribute('id').substr(5);
    var widgetId = tmp.substr(0, tmp.indexOf('_'));
    var starNbr = tmp.substr(tmp.indexOf('_')+1);
    new Ajax.Request('./php/submitRating', {
      method: 'get',
      parameters: {ratingID: widgetId, value: starNbr},
      onSuccess: function(transport) {
          alert(transport.responseText);
      }
    });
}

function prototypeInit()
{
    init_rating();
    $$('.rating').each(function(n){
                        n.immediateDescendants().each(function(c){
                            Event.observe(c, 'click', submitRating);
                        });
                    });
}

Event.observe(window, 'load', prototypeInit);
</script>

Conclusion

You can download a fully functional finished script, using the jQuery framework, from here.
I hope that you learned something new and that by now, you should have a fully functional Ajax rating widget. Once again, I'd like to specify that my intention is to show you an approach to coding with Ajax and less to provide you with a finished script. I hope I could show you how I separated the steps into different tasks and how each task was solved, using several different JavaScript frameworks. If you have anything to add, feel free to drop me a note and I'll make sure to include any necessary changes into this tutorial.