Rating for products, blog posts, the news is very important for various reasons. It attracts audiences and increases the sales if a product has a good rating along with the product details on the E-commerce website. Most of all, Google uses the rating to rank the websites in the search result. Online shoppers use the rating and reviews to make a decision before making any purchases.
In this example, I will show you how to implement a simple rating for movies application and store the responses into the database and retrieve the result with AJAX call. Let’s begin.
The finished product will be something like this.
Here is my table structure to store the rating responses from users. You can see a very simple movies table linked with the rating table using a foreign key.
I am using Code First approach to create the database and tables. Here are the two models for the Movies and StarRatings table.
public class Movie { public int ID { get; set; } [StringLength(60, MinimumLength = 3)] public string Title { get; set; } // [Display(Name = "Release Date"), DataType(DataType.Date)] //cover everything in one line [Display(Name = "Release Date")] [DataType(DataType.Date)] [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)] public DateTime ReleaseDate { get; set; } [RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")] [Required] [StringLength(30)] public string Genre { get; set; } [Range(1, 100)] [DataType(DataType.Currency)] public decimal Price { get; set; } [Display(Name = "Description")] [AllowHtml] public string Description { get; set; } [RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")] [StringLength(5)] public string Rating { get; set; } public byte[] BarcodeImage { get; set; } public string Barcode { get; set; } public string ImageUrl { get; set; } public string CategoryVal { get; set; } public IEnumerable<int> SelectedCat { get; set; } public int RateCount { get { return ratings.Count; } } public int RateTotal { get { return (ratings.Sum(m => m.Rate)); } } public virtual ICollection<StarRating> ratings { get; set; } }
In the above model, I have captured the “Rating” total and “Rating Count” so that I can fetch them on the model binding.
public class StarRating { [Key] public int RateId { get; set; } public int Rate { get; set; } public string IpAddress { get; set; } public int MovieId { get; set; } [ForeignKey("MovieId")] public virtual Movie movie { get; set; } }
The above star rating model is very simple. You can see the relationship created with the domain model Movie. I have also specified the Foreign Key to use the specific key instead of generic one.
Once the models have been created, we can now bind the Rating on the list view. Here is the list partial view code –
@model IEnumerable<WebApplication1.Models.Movie> @using WebApplication1.Helpers @* @using (Html.BeginForm("Index", "Movies", FormMethod.Get)) to pass the query in the url param*@ <div class="row"> <table class="table table-bordered table-hover" id="moviesTable"> <thead> <tr> <th> @Html.DisplayNameFor(model => model.ID) </th> <th> @Html.DisplayNameFor(model => model.Title) </th> <th> @Html.DisplayNameFor(model => model.ReleaseDate) </th> <th> @Html.DisplayNameFor(model => model.Genre) </th> <th> @Html.DisplayNameFor(model => model.Price) </th> <th> @Html.DisplayNameFor(model => model.Rating) </th> <th> @Html.DisplayNameFor(model => model.Barcode) </th> <th> @Html.DisplayNameFor(model => model.BarcodeImage) </th> <th></th> </tr> </thead> <tbody> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.ID) </td> <td> @Html.DisplayFor(modelItem => item.Title) <br /> <img src="~/images/EmptyStar.png" alt="Star Rating" align="middle" id="1" class="rating" mid="@item.ID" /> <img src="~/images/EmptyStar.png" alt="Star Rating" align="middle" id="2" class="rating" mid="@item.ID" /> <img src="~/images/EmptyStar.png" alt="Star Rating" align="middle" id="3" class="rating" mid="@item.ID" /> <img src="~/images/EmptyStar.png" alt="Star Rating" align="middle" id="4" class="rating" mid="@item.ID" /> <img src="~/images/EmptyStar.png" alt="Star Rating" align="middle" id="5" class="rating" mid="@item.ID" /> (@item.RateCount) @{if (item.RateCount > 0) { decimal a= @item.RateAvg / @item.RateCount; <span class="avr"> @a</span> } }<br /> <div class="result"></div> </td> <td> @Html.DisplayFor(modelItem => item.ReleaseDate) </td> <td> @Html.DisplayFor(modelItem => item.Genre) </td> <td> @Html.DisplayFor(modelItem => item.Price) </td> <td> @Html.DisplayFor(modelItem => item.Rating) </td> <td> @Html.DisplayFor(modelItem => item.Barcode) </td> <td> <img src="~/Movies/ShowPhoto/@item.ID" /> </td> <td> <div class="btn-group btn-group-xs"> <a href="#" class="btn btn-danger delete-row" data-item="@item.ID"><i class="fa fa-trash-o"></i></a> <a href="@Url.Action("Edit", new { id = item.ID } )" class="btn btn-info"> <i class="glyphicon glyphicon-pencil"></i> Edit </a> <a href="@Url.Action("Details", new { id = item.ID } )" class="btn btn-success"> <i class="glyphicon glyphicon-eye-open"></i> View </a> <a href="@Url.Action("DeleteInline", new { id = item.ID } )" class="btn btn-danger" onclick="return confirm('Are you sure to delete?')"> <i class="glyphicon glyphicon-trash"></i> Delete</a> @Html.NoEncodeActionLink("<span class='glyphicon glyphicon-pencil'></span>", "Edit", "EditModal", "Movies", routeValues: new { id = item.ID }, htmlAttributes: new { data_modal = "", @class = "btn btn-default" }) </div> </td> </tr> } </tbody> </table> </div>
I am using separate image files for the “filled in star” and “empty star”. The empty star gets replaced when a user hovers on it and vice versa. Here is the jQuery code to implement the hover functionality
<script type="text/javascript"> $(function () { $("img.rating").mouseover(function () { giveRating($(this), "FilledStar.png"); $(this).css("cursor", "pointer"); }); $("img.rating").mouseout(function () { giveRating($(this), "EmptyStar.png"); refilRating($(this)); }); $("img.rating").click(function (e) { // $("img.rating").unbind("mouseout mouseover click"); $(this).css('color', 'red'); // alert(e.currentTarget + ' was clicked!'); // call ajax methods to update database var url = "/Movies/PostRating?rating=" + parseInt($(this).attr("id")) + "&mid=" + parseInt($(this).attr("mid")); $.post(url, null, function (data) { $(e.currentTarget).closest('tr').find('div.result').text(data).css('color','red') // $("#result").text(data); }); }); }); function giveRating(img, image) { img.attr("src", "/Images/" + image) .prevAll("img.rating").attr("src", "/Images/" + image); } function refilRating(img1) { var rt = $(img1).closest('tr').find("span.avr").text(); var img = $(img1).closest('tr').find("img[id='" + parseInt(rt) + "']"); img.attr("src", "/images/FilledStar.png").prevAll("img.rating").attr("src", "/images/FilledStar.png"); } </script>
Now, this should work fine. When you hover over the empty stars, it will be replaced with the filled-in stars. It also replaces the previous stars based on cursor position.
Movies Controller
I have the following code to generate listview in the movies controller.
public ActionResult List(string movieGenre, string searchString) { //bind the genre list in the dropdown list var GenreLst = new List<string>(); var GenreQry = from d in db.Movies orderby d.Genre select d.Genre; GenreLst.AddRange(GenreQry.Distinct()); ViewBag.movieGenre = new SelectList(GenreLst); //string searchString = id; var movies = from m in db.Movies select m; if (!String.IsNullOrEmpty(searchString)) { movies = movies.Where(s => s.Title.Contains(searchString)); } if (!string.IsNullOrEmpty(movieGenre)) { movies = movies.Where(x => x.Genre == movieGenre); } return PartialView("_list", movies.ToList()); }
You can ignore some of the codes related to keyword search here that not relevant to this example.
When a user clicks on a star it makes an AJAX post request and returns a result. It stores the user response in the database and returns a message whether the request was successful or not. Displays the message inside a span. Review the ajax request below.
$("img.rating").click(function (e) { // $("img.rating").unbind("mouseout mouseover click"); $(this).css('color', 'red'); // call ajax methods to update database var url = "/Movies/PostRating?rating=" + parseInt($(this).attr("id")) + "&mid=" + parseInt($(this).attr("mid")); $.post(url, null, function (data) { $(e.currentTarget).closest('tr').find('div.result').text(data).css('color','red') // $("#result").text(data); }); });
Here is the “PostRating” action inside the Movies controller.
[AcceptVerbs(HttpVerbs.Post)] public JsonResult PostRating(int rating, int mid) { //save data into the database StarRating rt = new StarRating(); string ip = Request.ServerVariables["REMOTE_ADDR"]; rt.Rate = rating; rt.IpAddress = ip; rt.MovieId = mid; //save into the database db.Ratings.Add(rt); db.SaveChanges(); return Json("You rated this " + rating.ToString() + " star(s)"); }
So far so good. It works as expected and storing the data into the database. You can see the total rating and average rating next to rating widget. It will be nice to highlight the current rating in the widget instead of displaying blank stars. So I have extended the functionality by adding a jQuery block which reads the rating result from the span text and bind the star based on the average result. Here is the extended javascript code for this –
$('#moviesTable > tbody > tr').each(function () { var av = $(this).find("span.avr").text(); if (av != "" || av != null) { // alert(av); // fillRating(parseInt(av)); var img = $(this).find("img[id='" + parseInt(av) + "']"); img.attr("src", "/images/FilledStar.png").prevAll("img.rating").attr("src", "/images/FilledStar.png"); } });
The complete code for the list view –
@model IEnumerable<WebApplication1.Models.Movie> @using WebApplication1.Helpers @* @using (Html.BeginForm("Index", "Movies", FormMethod.Get)) to pass the query in the url param*@ <div class="row"> <table class="table table-bordered table-hover" id="moviesTable"> <thead> <tr> <th> @Html.DisplayNameFor(model => model.ID) </th> <th> @Html.DisplayNameFor(model => model.Title) </th> <th> @Html.DisplayNameFor(model => model.ReleaseDate) </th> <th> @Html.DisplayNameFor(model => model.Genre) </th> <th> @Html.DisplayNameFor(model => model.Price) </th> <th> @Html.DisplayNameFor(model => model.Rating) </th> <th> @Html.DisplayNameFor(model => model.Barcode) </th> <th> @Html.DisplayNameFor(model => model.BarcodeImage) </th> <th></th> </tr> </thead> <tbody> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.ID) </td> <td> @Html.DisplayFor(modelItem => item.Title) <br /> <img src="~/images/EmptyStar.png" alt="Star Rating" align="middle" id="1" class="rating" mid="@item.ID" /> <img src="~/images/EmptyStar.png" alt="Star Rating" align="middle" id="2" class="rating" mid="@item.ID" /> <img src="~/images/EmptyStar.png" alt="Star Rating" align="middle" id="3" class="rating" mid="@item.ID" /> <img src="~/images/EmptyStar.png" alt="Star Rating" align="middle" id="4" class="rating" mid="@item.ID" /> <img src="~/images/EmptyStar.png" alt="Star Rating" align="middle" id="5" class="rating" mid="@item.ID" /> (@item.RateCount) @{if (item.RateCount > 0) { decimal a= @item.RateAvg / @item.RateCount; <span class="avr"> @a</span> } }<br /> <div class="result"></div> </td> <td> @Html.DisplayFor(modelItem => item.ReleaseDate) </td> <td> @Html.DisplayFor(modelItem => item.Genre) </td> <td> @Html.DisplayFor(modelItem => item.Price) </td> <td> @Html.DisplayFor(modelItem => item.Rating) </td> <td> @Html.DisplayFor(modelItem => item.Barcode) </td> <td> <img src="~/Movies/ShowPhoto/@item.ID" /> </td> <td> <div class="btn-group btn-group-xs"> <a href="#" class="btn btn-danger delete-row" data-item="@item.ID"><i class="fa fa-trash-o"></i></a> <a href="@Url.Action("Edit", new { id = item.ID } )" class="btn btn-info"> <i class="glyphicon glyphicon-pencil"></i> Edit </a> <a href="@Url.Action("Details", new { id = item.ID } )" class="btn btn-success"> <i class="glyphicon glyphicon-eye-open"></i> View </a> <a href="@Url.Action("DeleteInline", new { id = item.ID } )" class="btn btn-danger" onclick="return confirm('Are you sure to delete?')"> <i class="glyphicon glyphicon-trash"></i> Delete</a> @Html.NoEncodeActionLink("<span class='glyphicon glyphicon-pencil'></span>", "Edit", "EditModal", "Movies", routeValues: new { id = item.ID }, htmlAttributes: new { data_modal = "", @class = "btn btn-default" }) </div> </td> </tr> } </tbody> </table> </div> <script type="text/javascript"> $(function () { $('#moviesTable > tbody > tr').each(function () { var av = $(this).find("span.avr").text(); if (av != "" || av != null) { // alert(av); // fillRating(parseInt(av)); var img = $(this).find("img[id='" + parseInt(av) + "']"); img.attr("src", "/images/FilledStar.png").prevAll("img.rating").attr("src", "/images/FilledStar.png"); } }); $("img.rating").mouseover(function () { giveRating($(this), "FilledStar.png"); $(this).css("cursor", "pointer"); }); $("img.rating").mouseout(function () { giveRating($(this), "EmptyStar.png"); refilRating($(this)); }); $("img.rating").click(function (e) { // $("img.rating").unbind("mouseout mouseover click"); $(this).css('color', 'red'); // alert(e.currentTarget + ' was clicked!'); // call ajax methods to update database var url = "/Movies/PostRating?rating=" + parseInt($(this).attr("id")) + "&mid=" + parseInt($(this).attr("mid")); $.post(url, null, function (data) { $(e.currentTarget).closest('tr').find('div.result').text(data).css('color','red') // $("#result").text(data); }); }); }); function giveRating(img, image) { img.attr("src", "/Images/" + image) .prevAll("img.rating").attr("src", "/Images/" + image); } function refilRating(img1) { var rt = $(img1).closest('tr').find("span.avr").text(); var img = $(img1).closest('tr').find("img[id='" + parseInt(rt) + "']"); img.attr("src", "/images/FilledStar.png").prevAll("img.rating").attr("src", "/images/FilledStar.png"); } </script>
HAPPY PROGRAMMING!!!
lovely
Hello, please help me. I try to implement this code and, in my case this don’t work.
Sure, let me know if you get stuck.
In my case when I do mouseout and mouseover nothing happens.
If I show you the code you might look over it, please.
You can check the browser console to see any java errors. It might be some minor error. Feel free to share you code.
I managed to solve it.Thank you! But now i have another question can you please tell me where is rate.avg?
$(“img.rating”).click(function (e) {
// $(“img.rating”).unbind(“mouseout mouseover click”);
$(this).css(‘color’, ‘red’);
// call ajax methods to update database
var url = “/Movies/PostRating?rating=” + parseInt($(this).attr(“id”)) + “&mid=” + parseInt($(this).attr(“mid”));
$.post(url, null, function (data) {
$(e.currentTarget).closest(‘tr’).find(‘div.result’).text(data).css(‘color’,’red’) // $(“#result”).text(data);
});
});
Which view does this code fall under? help
Hi! Could you please send me the used images?