Yii comes with a CStarRating widget, it is something visitors probably is familiar with since youtube and many other sites use something similair.
But how to use it?
A demo of the finished product is here: http://demo.wedson.se/howto
*Please note, this webhost is very slow imho so it can be some delay when voting,but atleast you can see how it all works before trying to follow this tutorial.
What we need and want is:
*The CStarWidget (duh)
*A model which can have a rating
*A callback that runs everytime a user click on the stars.
*A backend controller action that can recieve data from the callback and update the database.
*The backend controller action also needs to return something to the widget.
*When the user has voted, the CStarWidget should become read-only ( CStarWidget has a built in option for this)
*If we put the CStarWidget in our views/modelName/_view.php it needs to get an unique name so we can know what row in the database we should update, where to show the confirmation message and which widget we should turn into read-only.
*We also want the widget to show the current average rating (and fill the stars accordingly, say a model has an average rating of 5 out of a maximum 10, we want 5 stars to be filled when the page loads)
*We want to show the current rating in decimal form, ie 7,46 aswell as the number of votes.
So we start by creating a table to store all ratings.
DROP TABLE IF EXISTS `tbl_rating`;
CREATE TABLE IF NOT EXISTS `tbl_rating` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`vote_count` int(11) NOT NULL DEFAULT '0',
`vote_average` varchar(50) CHARACTER SET armscii8 COLLATE armscii8_bin NOT NULL DEFAULT '5',
`vote_sum` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
Note: this is not optimized for performance.
Lets say we want the user to give a rating to our blog posts.
We add a column in our posts table, and name it rating_id.
We then go to protected/models/post.php and define a relation:
‘rating’=>array( self::BELONGS_TO, ‘Rating’, ‘rating_id’),
In posts/controller we create a new action.
This action can recieve an id from an ajaxRequest, updates the total vote count and calculate the new average and after saving the new data,it sends some information back to the view.
public function actionRating()
{
if ( Yii::app()->request->isAjaxRequest )
{
$rating = Rating::model()->findByPk($_GET['id']);
$rating->vote_count = $rating->vote_count + 1;
$rating->vote_sum = $rating->vote_sum + $_GET['val'];
$rating->vote_average = round($rating->vote_sum / $rating->vote_count,2);
if ( $rating->save() )
{
echo CJSON::encode( array (
'status'=>'success',
'div'=>'Thank you for voting!',
'info'=>"Rating: " . $rating->vote_average ." " . $rating->vote_count . " votes",
) );
}
}
}
We then go to protected/views/post/_view.php
This is where we put the CStarWidget, since the file is called once for each post we have to assign an unique name for the widget. We also need unique div ids so we can present the confirmation at the right place.
<div id="rating_info_<?=$data->rating_id?>">
<?php
if ( $rating = Rating::model()->findByPk($data->rating_id) ) //if the record has an rating_id we echo it
{
echo "Rating: <strong>" . $rating->vote_average ."</strong>";
echo " " . $rating->vote_count . " votes";
}
?>
</div>
<?php // rating
$this->widget('CStarRating',array(
'name'=>'rating'.$data->rating_id, // an unique name
'starCount'=>10,
'readOnly'=>false,
'resetText'=>'',
'value'=>round($rating->vote_average,0), // this makes the widget shows the current rating rounded to //closest number(1,2,3,4,5,6,7,8,9 or 10)
'callback'=>' // updates the div with the new rating info, displays a message for 5 seconds and makes the //widget readonly
function(){
url = "/post/rating";
jQuery.getJSON(url, {id: '.$data->rating_id.', val: $(this).val()}, function(data) {
if (data.status == "success"){
$("#rating_success_'.$data->rating_id.'").html(data.div);
$("#rating_success_'.$data->rating_id.'").fadeIn("slow");
var pause = setTimeout("$(\"#rating_success_'.$data->rating_id.'\").fadeOut(\"slow\")",5000);
$("#rating_info_'.$data->rating_id.'").html(data.info);
$("input[id*='.$data->rating_id.'_]").rating("readOnly",true);
}
});}'
));
?>
<div id="rating_success_<?=$data->id;?>" style="display:none"></div> <!-- the div in which the confirmation message is shown-->
And this is it! This way you can use one rating table for as many models as you want.
NOTE! I wrote the comments for the code here on the forum, so to be sure please remove them when u understand the code so you do not get any weird errors!
Hope it helps someone!
//Sampa