<form method="POST" action="/rate.php" onsubmit="return rateclick(this);"> <span class="rating">Rating: 3.00/5 <input type="hidden" name="id" value="9999"></input> <input type="hidden" name="type" value="3"></input> | Rate: (bad) <input onclick="this.form.submitButton = 1;" type="image" src="images/logo8x8.png" name="A" title="1 - Utter crap"></input> <input onclick="this.form.submitButton = 2;" type="image" name="B" src="images/logo8x8.png" title="2 - Mostly crap"></input> <input onclick="this.form.submitButton = 3;" type="image" name="C" src="images/logo8x8.png" title="3 - Tolerable"></input> <input onclick="this.form.submitButton = 4;" type="image" name="D" src="images/logo8x8.png" title="4 - Does not suck"></input> <input onclick="this.form.submitButton = 5;" type="image" name="E" src="images/logo8x8.png" title="5 - True genius"></input> (good) </span> </form>
The form above is a standard form with images. The "image" input type will submit the form when clicked. If Javascript is not installed, then the form will submit to the regular ratings page. If it is installed, though, then it will call the Javascript function rateclick(), passing the form itself as an argument.
The Javascript has two parts. Part 1 sets up IE to use the XMLHTTP ActiveX control just like Mozilla's and Safari's XMLHttpRequest object. When doing cross-browser crap like this, it's best to find some common ground and code from there, rather than doing fifty million browser type checks. Part 2 defines a function that is called when the above form is submitted. If the .innerHTML property is not defined, then the function will return true, which means "submit the form like normal". If an XMLHttpRequest object cannot be created, again the function will return true. If any exception is caught in the javascript, then once again it will return true.
Next step is to set up the XMLHttpRequest object. The object has a method called onreadystatechange, which defines the function to call when the state of the request has changed. An anonymous function is used for this. In this function, the state and status is checked. A state of 4 means that the response is loaded and parsed. A status of 200 is the HTTP status code indicating everything is fine. If the status is 4 and everything is fine, then the function uses the .innerHTML method to set the text inside the rating span to reflect the new rating.
Next, the post request is made to ratexml.php. This is the server-side component to this. The Content-Type request header is set to application/x-www-form-urlencoded. This can really be any content type being sent, including text/xml, if you really really wanted to send XML. Finally, the request is sent with URL encoded parameters id, type, and rating. The function then returns false, which means "do not submit the form, I took care of it"
// Cross platform XMLHttpRequest
if(window.ActiveXObject && !window.XMLHttpRequest) // If it's not defined
{
window.XMLHttpRequest = function()
{
var msxmls = new Array(
'Msxml2.XMLHTTP.5.0',
'Msxml2.XMLHTTP.4.0',
'Msxml2.XMLHTTP.3.0',
'Msxml2.XMLHTTP',
'Microsoft.XMLHTTP');
for (var i = 0; i < msxmls.length; i++) // Try creating each type in order
{
try
{
return new ActiveXObject(msxmls[i]);
}
catch (e)
{
}
}
return null; // No XMLHttpRequest
};
}
function rateclick(e)
{
try
{
var rating = e.submitButton;
var elSpan = e.firstChild;
var id = e.elements[0].value;
var type = e.elements[1].value;
if(!elSpan.innerHTML) // If innerHTML is not defined, bail.
{
return true;
}
var xmlRequest = new XMLHttpRequest();
if(xmlRequest == null) // If XMLHttpRequest is not defined, bail.
{
return true;
}
xmlRequest.onreadystatechange = function() // Do this when the status changes.
{
if(xmlRequest.readyState == 4 && (xmlRequest.status == 200))
{
elSpan.innerHTML = "Rating: " + xmlRequest.responseText + "/5"; // Update the span text.
}
};
xmlRequest.open("POST", "ratexml.php"); // POST to our backend script
xmlRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); // Set the correct content type
xmlRequest.send("id=" + id + "&type=" + type + "&rating=" + rating + "\r\n"); // Send the rating information
return false; // Done
}
catch(exception)
{
return true; // Bail
}
}
The backend receives the posted variables, makes some database calls, and then spits back the new rating. If it can't figure out what the user wanted to rate, then it just spits back an error.
<?php
include("common.php"); /* Ron's common stuff. Don't worry */
header("Content-type: text/plain"); /* Plain text is returned. This could also be text/xml or text/html or whatever */
function PrintRating($itemid, $itemtype) /* Select the rating from the DB and return it. */
{
$query = "SELECT IF((Ratings.RatingSum / Ratings.RatingNumber) IS NULL, 0, (Ratings.RatingSum / Ratings.RatingNumber)) as Rating FROM Ratings WHERE ItemID=$itemid AND Type=$itemtype";
$result = mysql_query($query) or die("Could not get latest rating: " . mysql_error());
$row = mysql_fetch_row($result);
print $row[0];
}
if(!isset($_POST["id"]) || !isset($_POST["type"]))
{
print "WTF, mate?"; /* No ID or Type. Not valid in my scheme of things */
exit;
}
$id = intval($_POST["id"]);
$type = intval($_POST["type"]);
$rating = intval($_POST["rating"]);
if($rating < 1 || $rating > 5)
{
PrintRating($id, $type); /* Nice try. Just return the rating */
exit;
}
/* See if the user has already rated. */
$query = "SELECT * FROM RatingVotes WHERE IP='" . $_SERVER["REMOTE_ADDR"] . "' AND VoteDate = CURRENT_DATE AND Type=$type AND ItemID=$id";
$result = mysql_query($query) or die("Could not get Vote IP: " . mysql_error());
if(mysql_num_rows($result) != 0)
{
PrintRating($id, $type); /* User already rated, so just print the rating and leave. */
exit;
}
/* Update the rating */
$query = "UPDATE Ratings SET RatingSum = RatingSum + $rating, RatingNumber = RatingNumber + 1 WHERE ItemID=$id AND Type=$type";
$result = mysql_query($query) or die("Could not vote: " . mysql_error());
$query = "INSERT INTO RatingVotes (IP, ItemID, VoteDate, Type) VALUES('" . $_SERVER["REMOTE_ADDR"] . "', $id, CURRENT_DATE, $type)";
$result = mysql_query($query) or die("Could not add IP: " . mysql_error());
PrintRating($id, $type); /* Print the new rating */
?>